Outline
Each remote system is an object.
With TGrid
, you can easily develop complicated network system, by considering each network system as an object, and interacting with each other through RPC (Remote Procedure Call). TGrid
defines this concept as "Object Oriented Network".
In this chapter, we'll remake the composite calculator system of Remote Object Call chapter again, but replace scientific and statistics calculators to remote system. Therefore, the composite calculator system will be consisted of three remote servers: "composite server", "scientific server" and "statistics server".
Let's see how TGrd
implements the "Object Oriented Network".
Demonstration
You can run the example program on Playground Website (opens in a new tab), or local machine.
git clone https://github.com/samchon/tgrid.example.object-oriented-network
npm install
npm start
Client Program
import { Driver, WorkerConnector } from "tgrid";
import { ICalcConfig } from "./interfaces/ICalcConfig";
import { ICalcEvent } from "./interfaces/ICalcEvent";
import { ICalcEventListener } from "./interfaces/ICalcEventListener";
import { ICompositeCalculator } from "./interfaces/ICompositeCalculator";
const EXTENSION = __filename.endsWith(".ts") ? "ts" : "js";
export const workerClientMain = async () => {
const stack: ICalcEvent[] = [];
const listener: ICalcEventListener = {
on: (evt: ICalcEvent) => stack.push(evt),
};
const connector: WorkerConnector<
ICalcConfig,
ICalcEventListener,
ICompositeCalculator
> = new WorkerConnector(
{ precision: 2 }, // header
listener, // provider for remote server
"process",
);
await connector.connect(`${__dirname}/composite.${EXTENSION}`);
const remote: Driver<ICompositeCalculator> = connector.getDriver();
console.log(
await remote.plus(10, 20), // returns 30
await remote.multiplies(3, 4), // returns 12
await remote.divides(5, 3), // returns 1.67
await remote.scientific.sqrt(2), // returns 1.41
await remote.statistics.mean(1, 3, 9), // returns 4.33
);
await connector.close();
console.log(stack);
};
Terminal$ npm start 30 12 1.67 1.41 4.33 [ { type: 'plus', input: [ 10, 20 ], output: 30 }, { type: 'multiplies', input: [ 3, 4 ], output: 12 }, { type: 'divides', input: [ 5, 3 ], output: 1.67 }, { type: 'sqrt', input: [ 2 ], output: 1.41 }, { type: 'mean', input: [ 1, 3, 9 ], output: 4.33 } ]
Server Programs
import { Driver, WorkerConnector, WorkerServer } from "tgrid";
import { ICalcConfig } from "./interfaces/ICalcConfig";
import { ICalcEventListener } from "./interfaces/ICalcEventListener";
import { IScientificCalculator } from "./interfaces/IScientificCalculator";
import { IStatisticsCalculator } from "./interfaces/IStatisticsCalculator";
import { SimpleCalculator } from "./providers/SimpleCalculator";
const EXTENSION = __filename.endsWith(".ts") ? "ts" : "js";
/// `CompositeCalculator` has two additional properties
///
/// - `scientific` from remote worker server
/// - `statistics` from remote worker server
class CompositeCalculator extends SimpleCalculator {
public readonly scientific: Driver<IScientificCalculator>;
public readonly statistics: Driver<IStatisticsCalculator>;
public constructor(props: {
config: ICalcConfig;
listener: Driver<ICalcEventListener>;
scientific: Driver<IScientificCalculator>;
statistics: Driver<IStatisticsCalculator>;
}) {
super(props.config, props.listener);
this.scientific = props.scientific;
this.statistics = props.statistics;
}
}
/// connect to remote worker server
const connect = async <T extends object>(
header: ICalcConfig,
listener: Driver<ICalcEventListener>,
file: string,
): Promise<Driver<T>> => {
const connector: WorkerConnector<ICalcConfig, ICalcEventListener, T> =
new WorkerConnector(header, listener, "process");
await connector.connect(file);
return connector.getDriver();
};
const main = async () => {
const server: WorkerServer<
ICalcConfig,
CompositeCalculator,
ICalcEventListener
> = new WorkerServer();
const config: ICalcConfig = await server.getHeader();
const listener: Driver<ICalcEventListener> = server.getDriver();
// constructor provider combining with remote worker-servers
const provider: CompositeCalculator = new CompositeCalculator({
config,
listener,
scientific: await connect<Driver<IScientificCalculator>>(
config,
listener,
`${__dirname}/scientific.${EXTENSION}`,
),
statistics: await connect<Driver<IStatisticsCalculator>>(
config,
listener,
`${__dirname}/statistics.${EXTENSION}`,
),
});
await server.open(provider);
};
main().catch((exp) => {
console.error(exp);
process.exit(-1);
});
Compose Provider
with Driver
of another remote system.
Looking at the "Composite Server", it is providing CompositeCalculator
to the "Client Program". By the way, the CompositeCalculator
is different with before chapter Remote Object Call. Properties scientific
and statistics
are composed with Driver<T>
of another remote system.
Therefore, if "Client Program" calls Driver<ICompositeCalculator>.scientific.sqrt(2)
function, it will be forwarded to the "Scientific Server", and "Composite Server" only intermediates the remote function call (network communication) between "Client Program" and "Scientific Server".
This is the "Object Oriented Network" of TGrid
.
Next Chapter
At next chapter, we'll learn how to integrate TGrid
with NestJS
.
import { TypedRoute, WebSocketRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import { Driver, WebSocketAcceptor } from "tgrid";
import { ICalcConfig } from "./api/interfaces/ICalcConfig";
import { ICalcEventListener } from "./api/interfaces/ICalcEventListener";
import { ICompositeCalculator } from "./api/interfaces/ICompositeCalculator";
import { IScientificCalculator } from "./api/interfaces/IScientificCalculator";
import { ISimpleCalculator } from "./api/interfaces/ISimpleCalculator";
import { IStatisticsCalculator } from "./api/interfaces/IStatisticsCalculator";
import { CompositeCalculator } from "./providers/CompositeCalculator";
import { ScientificCalculator } from "./providers/ScientificCalculator";
import { SimpleCalculator } from "./providers/SimpleCalculator";
import { StatisticsCalculator } from "./providers/StatisticsCalculator";
@Controller("calculate")
export class CalculateController {
/**
* Health check API (HTTP GET).
*/
@TypedRoute.Get("health")
public health(): string {
return "Health check OK";
}
/**
* Prepare a composite calculator.
*/
@WebSocketRoute("composite")
public async composite(
@WebSocketRoute.Acceptor()
acceptor: WebSocketAcceptor<
ICalcConfig,
ICompositeCalculator,
ICalcEventListener
>,
@WebSocketRoute.Header() header: ICalcConfig,
@WebSocketRoute.Driver() listener: Driver<ICalcEventListener>
): Promise<void> {
const provider: CompositeCalculator = new CompositeCalculator(
header,
listener
);
await acceptor.accept(provider); // ACCEPT CONNECTION
acceptor.ping(15_000); // PING REPEATEDLY TO KEEP CONNECTION
}
/**
* Prepare a simple calculator.
*/
@WebSocketRoute("simple")
public async simple(
@WebSocketRoute.Acceptor()
acceptor: WebSocketAcceptor<
ICalcConfig, // header
ISimpleCalculator, // provider for remote client
ICalcEventListener // provider from remote client
>
): Promise<void> {
const header: ICalcConfig = acceptor.header;
const listener: Driver<ICalcEventListener> = acceptor.getDriver();
const provider: SimpleCalculator = new SimpleCalculator(header, listener);
await acceptor.accept(provider); // ACCEPT CONNECTION
acceptor.ping(15_000); // PING REPEATEDLY TO KEEP CONNECTION
}
/**
* Prepare a scientific calculator.
*/
@WebSocketRoute("scientific")
public async scientific(
@WebSocketRoute.Acceptor()
acceptor: WebSocketAcceptor<
ICalcConfig,
IScientificCalculator,
ICalcEventListener
>
): Promise<void> {
const header: ICalcConfig = acceptor.header;
const listener: Driver<ICalcEventListener> = acceptor.getDriver();
const provider: ScientificCalculator = new ScientificCalculator(
header,
listener
);
await acceptor.accept(provider); // ACCEPT CONNECTION
acceptor.ping(15_000); // PING REPEATEDLY TO KEEP CONNECTION
}
/**
* Prepare a statistics calculator.
*/
@WebSocketRoute("statistics")
public async statistics(
@WebSocketRoute.Acceptor()
acceptor: WebSocketAcceptor<
ICalcConfig,
IStatisticsCalculator,
ICalcEventListener
>
): Promise<void> {
const header: ICalcConfig = acceptor.header;
const listener: Driver<ICalcEventListener> = acceptor.getDriver();
const provider: IStatisticsCalculator = new StatisticsCalculator(
header,
listener
);
await acceptor.accept(provider); // ACCEPT CONNECTION
acceptor.ping(15_000); // PING REPEATEDLY TO KEEP CONNECTION
}
}