Skip to content

Architecture

Flynn, Conor edited this page Dec 15, 2022 · 6 revisions

Introduction

This section covers the general overview of the DeFi-Data-Engine's architecture including but not limited to:

  • General system overview
  • Internal routing architecture explanation
  • Routing architecture implementation

General System Overview

The DeFi-Data-Engine (DFDE) is a native Java application designed to support the transferring, processing, and storage of high frequency data streams. To accomplish this, the system has been designed with multiple considerations such as: expansion into other sources, multi-user threading capabilities, and language agnostic understanding.

There are three main component types to the DFDE: the Java Applications, the External (R) Applications, and the External Data Sources. The Java Applications are the two primary applications consisting of the main engine architecture and the REST API. The External Applications are any external programs that utilize the engine to extract data. Finally External Data Sources are implemented sources from which the engine can extract data and parse it into the DFDE ecosystem. Below is a high level overview of the DFDE and all of the primary components that interact with the engine. image

The primary focus of this section will be on the "Engine Processes" component which houses all major internal communication within the engine. This will be further expanded upon in later sections, breaking down the individual processes, how they are implemented, and how they can be expanded upon. Also note that R is not the only supported language, however it is the primary language utilized by the lab and therefore will be the most heavily documented. Please note that any language that supports Socket connections and REST API calls can utilize the DFDE.

Internal Routing Architecture

Routers

Overview

To mediate and standardize all communication across different processes within the engine, a process similar to a network has been implemented (hence the use of "routers" and "packets"). All routers are connected through a "manager" which stores and groups all routers into a single object which allows for communication between all of them. Each router has a unique id (uuid) and a tag which are used to identify it. For example, the Core process has the uuid core and the tag 'COR'. Each tag is denoted by a three character string (although not required it is standard practice to save memory).

Each process has sub-processes as well denoted by four character strings. These sub-processes carry out actions within the engine and are the standard way processes communicate with one another. Below we explore their implementation and how they can be created.

Framework

The abstract Router class is listed in the org.framework.router package and holds the framework for implementing a new process. Below are all core methods and their descriptions used within the class. Please note all methods not listed with abstract are implemented and just not shown in this example.

// base constructor
public Router(String uuid, String tag) {}

// constructor for auto-connecting the Router to an existing Manager
public Router(String uuid, String tag, Manager manager) {}

// returns uuid of the Router
public final String getUUID() {}

// returns tag of the Router
public final String getTag() {}

// returns the Manager the Router is connected to
public final Manager getManager() {}

// connects the Router to a new Manager
public final void setManager(Manager manager) {}

// connects list of Routers to this Router
public final void connect(Router... routers) {}

// checks to see if a Router with the given tag is connected to this Router
public final boolean isConnected(String tag) {}

// checks to see if a Router is connected to this Router
public final boolean isConnected(Router router) {}

// returns all connected Routers to this Router
public final Collection<String> connectedTags() {}

// sends a Packet with the given data to a connected Router
// returns a Response object
public final Response send(String tag, String sub_tag, HashMap<String, String> data) {}

// sends a Packet with the given data to a connected Router
// returns a Response object
// note all data is in the format ["key_1", "value_1", "key_2", "value_2",..., "key_n", "value_n"]
public final Response send(String tag, String sub_tag, String... data) {}

// function that receives an incoming Packet
public final Response receive(Packet packet) {}

// function for adding a sub-process to the Router
public final void addProcess(String subtag, Method method) {}

// function used for handling incoming processes
// users do NOT need to interact with this function
private final Response process(Packet packet) {}

// function which auto adds any function labeled "process----"
// to the Router which can then be accessed
// for example the function "processSUBS" will be added to the function
// under the sub_tag "SUBS" that can be called from other Routers
private final void defineProcesses() throws NoSuchMethodException, SecurityException {}

Examples

Defining Router Objects

public class Router1 extends Router {
  public Router1() {
    super("router1", "RT1");
  }
}

public class Router2 extends Router {
  public Router2() {
    super("router2", "RT2");
  }

  public Response processEXPL(Packet packet) {
    System.out.println(packet.getData("sample"));
    return ResponseFactory.response200();
  }
}

Connecting the Routers

Router1 rt1 = new Router1();
Router2 rt2 = new Router2();
  
rt1.connect(rt2);
rt1.isConnected("RT2"); // true
}

Sending a Packet

// this call will print the data stored under "sample"
// and then return a 200 response
rt1.send("RT2", "EXPL", "sample", "this-is-printed").code(); // 200

Packets

Overview

Packets are the standard communication source used by the engine. Every Router object is able to easily handle and process a Packet object, allowing for easy communication between processes and expansion of the engine. Packets contain four data components: sender, tag, sub_tag, and data`.

The sender object refers to the Router that sent the Packet. This can be used for verification purposes (say for example an important process can only receive packets from a verified process) to make sure no rouge packets interfere with various communications. The tag is used to identify the destination Router that the packet is sent to. The sub_tag denotes the sub-process the Packet is to be sent to and interact with. Finally the data object stores all data the packet is transmitting to the sub-process.

Packets are created through the Router.send(String tag, String sub_tag, HashMap<String, String> data) and Router.send(String tag, String sub_tag, String... data) functions from which they are automatically sent to the appropriate locations.

Framework

The Packet class is listed in the org.framework.router class and can only be created through the given factory methods packet(Router router, String tag, String sub_tag, HashMap<String, String> data) and packet(Router router, String tag, String sub_tag, String... data). Below are all functions defined within the Packet class.

// private constructor
private Packet(Router router, String tag, String sub_tag, HashMap<String, String> data) {}

// returns sending Router's tag
public final String getSender() {}

// returns destination Router's tag
public final String getTag() {}

// checks if data object contains given key
public final boolean containsKey(String key) {}

// returns HashMap containing all data
public final HashMap<String, String> getData() {}

// returns data point under given key
public final String getData(String key) {}

// validates that all passed keys are contained in the packet
// if not returns a string of the missing key
public final String validate(String... keys) {}

// factory method for creating packet with pre-defined HashMap data object
public static Packet packet(Router router, String tag, String sub_tag, HashMap<String, String> data) {}

// factory method for creating packet from list of strings
public static Packet packet(Router router, String tag, String sub_tag, String... data)

Routing Architecture Implementation

This subsection covers all currently implemented packet communications between all processes. This includes requesters, destinations (tag), sub-processes (sub_tag), passed variables, the return, the possible response codes, and a description. Please note that all processes and their tags will be explained in a later section.

Requesters Tag Sub_Tag Variables Return Description Response Codes
CRL, OUT SRC EXSR source: String exists: boolean Determines if external stream source exists. 200, 500
CRL, OUT SRC EXST key: String exists: boolean Determines if an external stream with the given tag exists 200, 500
OUT SRC INIT source: String, auth_data: String[] key: String Initializes a new stream with the given data and authorizes it. 200, 420, 422, 500
CRL, OUT SRC IATH key: String is_authorized: boolean Determines if a stream with the given key is authorized. 200, 421, 500
CRL, OUT SRC IATV key: String key: String is_active: boolean Determines if a stream is currently active.
OUT SRC EXEC key: String success: boolean Executes the stream with the given key. 200, 421, 423, 424, 500
CRL, OUT SRC KILL key: String success: boolean Kills the stream with the given key. 200, 421, 425, 500
OUT SRC SUBS key: String, subscription: String n/a Subscribes to a new type of data embedded within the data stream. 200, 421, 425, 426, 427
OUT SRC RQST key: String, request: String, start_date: String, end_date: String, query: String[] data: String[] 200, 421, 423, 428, 429, 441, 445, 446, 447, 449