H B-11 avalanche reaction on nuclear fusion

HB11 Energy has demonstrated a 'material' number of fusion reactions, producing ten times more fusion reactions than expected.

Application Interoperability

  • By Artem V. Shamsutdinov
  • April 4th, 2022

A key feature of AIRport framework is the ability of applications to interoperate. This is possible because of two reasons:

  1. For reads, applications can create @ManyToOne relationships to tables of existing schemas. And any application can query for all data in device's database (across all application schemas, if permitted).
  2. AIRport persists all entities across application schemas and invokes all necessary validation and business logic.

Read Queries

Management of read queries is pretty strait forward. The raw AIRport framework is not concerned with it since no modifications are made. For Turbase, it is possible figure out what part of the query belongs to which application and apply necessary permissions as well as split advertisement revenue between them.

Modifications

My initial thoughts on how to support saving of nested entities was to have support for nested save calls. However that does not support execution of any validation and business logic that applications may want to do before the entities are saved.

Instead, the final version of Application Interoperability allows applications to call APIs of other applications to execute modificaition logic for the apps that manage nested entities. Ability to invoke APIs of other applications would be implemented in any case. However, relying on it for persistence of nested objects makes the overall approach more intuitive. To accommodate for that objects that are passed into a save call of a particular application will be ignored if they do not belong to that application. This of course means that the persistence of nested objects will have to be done in the right order to work for newly created entities.

Project Types

The final version of Application Interoperability has 3 types of projects for each application:

  1. Default
  2. Runtime
  3. Client

Default

The default project is what the developer creates explicitly. It contains all of the core code for the application: DDL, DAOs, API. It also contains generated sources for the query API and other auxiliary logic. It's main file is "src/generated/api.ts". It does not include any of the non-generated code and instead includes generated wrappers around each of the API classes for the project.

The default project does include the generated query objects. This is because the default project is what other applications use as a dependency. Other apps can either call the pre-defined API methods (in which case the calls are passed back to the framework and are executed in the runtime version of the app), or run queries using generated query objects.

Runtime

The runtime project is nested within the default project inside a 'runtime' directory. It will be completely generated and will have all of the dependencies needed to build a runtime application. It will also contain the build script and generate a bundle that will deploy the full version of the application. This is the project that actually includes the sources from the DDLs, DAOs and APIs of the default project because its the 'runtime' project that will actually be executing the application code.

Client

The client project is also nested within the default project ( inside the 'client' directory). It will also be completely generated and will include a non-injected wrappers around all of the app's APIs. These are the wrappers that will be called by external clients. They are not injected by AIRport dependency injection framework since the external client does not run inside AIRport - the wrappers have to be 'new'ed to be used. The wrappers will forward the calls to the AIRport framework (running in a separate tab or as a native application), which will forward them to the application (which is either nested in an IFrame, inside the framework tab, or is running in a V8 isolate of the native application for the framework). The client project also includes all of the generated interfaces for the DDL objects.

I'm still thinking of whether to include the DDL objects themselves. Theoretically they shouldn't have any business logic and the only benefit of including them is ability to use entity names (instead of the names of generated interfaces, which are prefixed with the letter 'I').

Transactions

Another issue that so far has not been decided on is transactionality. My prior thoughts were to either add support for @Transactional() methods or to allow for explicit calls to the "transactional" function. However for AIRport all transactions always go into the same database and there is no need for JPA style transactions. Really all calls made via a particular API should always belong to the same transaction. And if that API call is nested in another API call, all of the operations in this API should be included in the transaction of the outer API call.

Hence, the final solution is to make every @Api() method transactional. If an @Api() method is called from another @Api() method it is automatically included in the transaction of the parent method. In the future support for @Transactional() methods may be included if the need for that arises.

To allow for multiple read calls to go through while a write @Api() call is in progress, @Api({ readOnly: true }) will be supported (eventually). A read-only @Api() call will be allowed to execute while a write-enabled @Api() call is in progress. If a read-only @Api() call attempts to execute a write statement it will throw an exception. If a write @Api() call is nested inside a read-only @Api() call, it will block and wait for any executing write @Api() calls before completing.

How it's done

The way this will be done is via keeping track of nested @Api() calls with a call stack. When the first @Api() call is made a transaction is started. That transaction is maintained until that @Api() call returns a value, at which point the transaction is committed. The nested @Api() call is stored in a stack of parent-child TRANSACTION_IDs, when a child @Api() call is started a child TRANSACTION_ID is generated and is passed into the the called application. When the called @Api() returns, it returns it's child TRANSACTION_ID and that call is popped off the stack.

This implementation also helps track @Api() calls from other UIs and Apps that are not nested in the current transaction. Such calls are put into a separate FIFO queue and are executed subsequently. Read-only calls are executed immediately.

Rolling Back

AIRport is based on SQLite, which supports SAVEPOINT and RELEASE TO statements. This enables fine grained handling of nested @Api() transactions. Whenever a nested non-read-only @Api() is called (and also for the outer most call) following command is issued:

SAVEPOINT $TRANSACTION_ID
When an @Api() successfully returns following command is issued:
RELEASE SAVEPOINT $TRANSACTION_ID
If an @Api() throws an exception following command is issued:
ROLLBACK TO SAVEPOINT $TRANSACTION_ID
This allows for automatic management transactions in a nested @Api() call, without having to rollback the entire transaction. To allow for management of savepoints within an @Api() call, the following() commands will be available:

const savePointId = savepoint()
release(savePointId)
rollbackTo(savePointId)