Declarative Runtime System
- The declarative runtime system accepts a statement that represents formal logic and translate to its semantic (technical) instructions, and as a result of execution, the runtime system changes values in the state and adjusts the dependency graph accordingly.
- The syntax of the runtime is declarative and separated from its technical implementation. That provides; (1) Defining the functional syntax in formal logic. (2) Flexibility of improving technical side without requiring changes in defined behaviors of the program
- Logical integrity of declarative statements is guaranteed by the runtime system.
- Declarative statements do not carry any technical details including a control flow, instead the runtime logically builds a control flow out of declarative statements.
- As a part of plasticity, all types of statements including business rules are considered as data so that statements can be added without requiring any further actions such as recompiling, restarting etc.
- The runtime persists each statement on storage and cumulates values in memory so that an external data store is no longer needed in order to store as well as retrieve values.
The declarative runtime system isolates a behavior definition of a program from its technical instructions and executes declarative statements, which represent logical intention without carrying any technical details. In this paradigm, there is no segregation regarding what data is or not, instead approaches how data is related with others so that any type of data including business rules can be added without requiring any additional actions such as compiling, configuring, restarting as a result of plasticity. The runtime executes a statement based on its relationship with other statements without definite flow, and the runtime builds a control flow of program logically out of declarative statements. So, as adding more and various statements, dependency graph of statements gets complexed and branched. Plasticity also shapes persistency such that because the runtime dynamically receives declarative statements, each transaction is persisted, and the final value is cumulated and available on memory. This persistency model provides faster read and faster write operations, but requires higher space complexity as a tradeoff. Since the runtime has persistent capabilities, an external database is not required in order to store business entities.
As moving toward higher end in programming language spectrum, programming languages and runtime systems abstract repetitive jobs and automate them in order to ease development. Each level promises own advantages along with tradeoffs, but advancing in distributed system and concurrency models, impact of tradeoffs can be minimized. However, among high level programming languages, different programming paradigms such as object-oriented, functional, event-driven etc. differentiate the solution.
Program behavior is usually defined by different group of people without complete technical details, and technical team is to translate the functional specifications into technical instructions. While programming, taken steps vary depends on programming paradigm, however, a system reaction to desired behavior has to match with how functionality described at very first.
Even though a programmer follows closely the pseudocode provided, extra steps and different paths have to be pursued based on hardware architecture, operating system, programming paradigm etc. Programming languages in different levels are attempt to minimize the impact of those differences, but still, expected behavior of the program is choreographed along with technical procedures. So, in order to accomplish functional steps from algorithmic point of view, the technical model has to be considered and planned accordingly.
The declarative runtime system separates business rules represented in formal logic from its technical implementation. This paradigm is implemented as a runtime system as supposed to compiler or programming language, and beyond the this layer implementation details are encapsulated.
In declarative programming, the declarative tier consists of declarative syntax of statement and its semantic implementation, which is is written in any fashion of high level programming language and fully in charge of control flow unlike the declarative syntax. Separating declaration from its definition provides flexibility in defining functional syntax that is meaningful to different set of people. In common practice, functional behavior along with technical details is written in a programming language, but it is limited within what has been provided by the language, and its syntax is usually highly technical, which may not suit well for less technical people, whose expertise more on domain instead. In this paradigm, the syntax of declaration is not enforced as long as logically meaningful, but for practicality, having similar syntax with modern programming language is favorable, but as defining more functionality, it may divert or similar syntax means something else in functional context.
Modern software development methodologies focus on functional requirements, which are considered true value of deliverable, and any technical details are usually encapsulated and shipped with minimum exposure. In advance cases, same functionalities are rewritten with changing underlying software architecture without noticed by a user. This perspective parallels with this paradigm and does not add, alter either conflict with processes of those methodologies. Indeed, it helps that functional requirements in some form that human being and machine can understand at the same thing. This concept is suggested as ubiquitous language in domain driven design. There are also commonalities with functional programming as well, which well respected paradigm and aims to declarate desired result without expressing technical flow. In fact, declarations can use functional programming since both of them are part of same family.
Nature of functional requirements are logical rather than sequential. Instead of declarative syntax runs in a definite flow, business rules are generalized so that at the any time of process, logical integrity is guaranteed by the runtime system. However, without intermediate transformation, it is responsibility of written program to have the integration.
Instructions of programming languages are based on how hardware performs instructions along with using logic gates, registers etc., but it does not necessarily mean it complies with formal logic all the time. For example, when assigning variable to another variable, a computer links the variable to memory block of pointed variable. As the value in memory block changes, value of variable also changes, however, if point variable is linked to another value in different memory block, assigned variable does not change automatically, but according to formal logic assigned variable should track the pointed variable instead. Memory blocks managed by computer operations do not carry any meaning in formal logic therefore intermediate interpreter like runtime system has to translate those into logical statements.
In imperative programming, since functional steps are embroiled in technical procedures, necessary code lines are repeated for each entity and its behavior in order to comply with formal logic as adding more business rules and entities. However, in the declarative runtime system, technical definition is generalized as defined in declaration so that technical procedure has to be written once and applies overall so that it reduces number of code lines needed.
Codes are organized by functional and technical capabilities so that group of people who are expert at own fields are isolated, especially, a group of people familiar with more domain knowledge can focus on functional behaviors. At the same time, regardless of underlying technical model, as finding improvement on technical side, functional side stays the same. This paradigm also enables solving technical problem in own place along with functional issues in their own. This helps organizations to scale teams accordingly, and for code maintainability, scaling is important aspect.
As the name suggests, the runtime system runs as daemon process and accepts an entry through operating system. As soon as an entry received, JIT compiler compiles to runnable statements, and hand it over to the call stack. Execution of the statement consists of 3 steps; updating state, adjusting dependency graph and storing, and as a result, the statement can produce more statements in order to provide logical integrity. Those produced statements will be added into the call stack as well for further cycles. Alternatively, a statement can return value when all logical transactions completed.
The runtime system is triggered with logic represented as string so that it compiles the string into runnable statements with just-in-time compilation model. Since the string can carry multiple logical statements as well as a result of execution can create more statements, the call stack queues all those statements and executes simultaneously as long as it does not break logical integrity. In order to do that, it builds execution plan based on dependencies of statements in the queue. Execution of statement changes value in the state and adjusts dependency graph, and both actions are based on a structure of statement. A result of execution may return extra statements, value or may not return anything at all, and the transaction ends when all statements in the call stack completed. One difference with call stack component in this paradigm from traditional call stack implementation is the call stack in this paradigm is not singleton, instead it is per transaction.
The state holds the final value, is built out of accumulated statements. These values are available in memory, but they are not storef so that at the beginning of the runtime process, it requires to build the state out of stored statements. There are potential improvements can be applied in order to speed up boot time like storing the state asynchronously, but it is still requires loading the state from storage and accumulating statements between the state store time and the latest statement time.
In declarative programming, a control flow is not managed by sequence of statements/instructions, instead the dependency graph is built out of declarative statements and draws a control flow logically based on relationship between statements. The backtracking mechanism is used to track logical flow so that after execution of a statement, its dependent statements will be mapped and adjusted accordingly. This part is required in order to achieve formal logic because all values must be logically meaningful as declared throughout the runtime system.
Call stacks are created per transaction, and statements in the transaction are queued in the call stack. After all statements in transaction are executed, the runtime ends the call stack. The main benefit of having a call stack is to help logical integrity especially when extra statements are created during a transaction due to backtracking mechanism. Call stacks do not integrate with underlying a call stack implementation of the programming language even though both works similar. However, since a statement can be logically not dependent another statement, instead of the call stack runs one at a time, the call stack starts multiple execution simultaneously as a part of execution plan in order to expedite the process time.
Traditionally, business rules are written as a part of coding and compile into binaries. Flexibility added with configuration, but even configuration is part of compiled binaries so that adding or modifying configuration requires to compile again. In this paradigm, business rules are considered as data, in fact, there is no segregation regarding what data is or not, but the matter is how data relates with others.
Plasticity of the runtime system enables program that can be run any type of data including business rules without requiring any additional actions such as compiling, configuring, restarting etc., and it is enabled by declarative programming. As adding more and various statements, dependency graph gets more complexed and branched, and since behavior of the program directly can changed, externalized configuration is not needed anymore.
Plasticity is not possible with traditional compiler because such compiler requires logic definition at compile time, instead just-in-time compilation model, which accepts string as an entry at runtime and compiles into runnable statements immediately, is required by the declarative runtime system in order to dynamically add business rules at anytime of process since declarative statement does not require any sequence.
The control flow in the runtime system is semantically build out of declarative statements. As the runtime system receives more statements, it adjusts the dependency graph and changes the direction of execution based on logical relationships between declarative statements. However, instructions in traditional compiler model are set at compiler time and cannot be changed unless compiled again.
Building a control flow dynamically out of declarative statements also enables the runtime to execute some statements simultaneously. Generally, multithreading is managed by written program and requires serious effort to operate correctly. In this paradigm, the runtime system applies multithreading wherever it is possible according to logical flow, and this accelerates the execution.
A control flow is constant in programming languages, which requires configuration in order to change behavior of the program without interrupting. Based on configuration, software may take different path, however all possible paths are definite, and any change on the flow, requires recompiling, restarting etc. However, declarative runtime system accepts business rules anytime as a result of plasticity, and a behavior of program can be configured according to business requirements without configuration. The runtime system executes a statement is based on relationship between rest of statements, and a flow of process changes as receiving more declarative statements.
Plasticity shapes persistency such that because the runtime dynamically receives declarative statements, each transaction is persisted, and final value is cumulated at the end of transaction. However, traditionally databases persist value only. Benefit of not storing just value is regardless of data operation of creating, updating, deleting, storing hard copy is appending operation, which is the fastest storage operation and takes constant time. At the same time, since accumulated value is available on the memory, and it does not require reading from storage anymore, which tremendously speeds up overall retrieving time. Typically, it must be chosen between faster read slow write or faster write slow read, however, in this paradigm both reading and writing operations are faster.
As a tradeoff, space complexity of memory and storage is higher than traditional model. This paradigm requires all accumulated values to be placed in a memory as well as all transactions to be persisted on a storage so that requirement of memory and storage space is much higher. Even though some algorithm can minimize space requirements such as storing partial state in storage, cumulating transactions etc. Nevertheless, these numbers are always going to be larger than traditional model, but those tradeoffs do not seem impediments in direction of industry. First, in-memory processing gets own attention as opposed to traditional database deployment model, and storages are excessively getting cost-effective.
Since the declarative runtime system can store state of object, there is no external database is required. Traditionally, database schema is designed according to business objects used in programming languages, and modern frameworks convert those business objects into database structure automatically. Thus, databases became persistent unit of business objects, and in common practice, programming languages or runtime systems do not persist state of object so that they require external database in order to store business objects, but that is not the case for the declarative runtime system.
Storing each declarative statement in order as received also enables they can be replayed for diagnostic purposes in future, and the runtime provides the exact same result. in contrast, since traditional model persists value only, further diagnostics are based on logging information, which is manually written in a program.