đź“– Guide Documents
Learn from Examples
Object Oriented Network

Outline

Object Oriented Network

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.

Terminal
git clone https://github.com/samchon/tgrid.example.object-oriented-network
npm install
npm start

Client Program

examples/object-oriented-network/src/client.ts
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

examples/object-oriented-network/src/composite.ts
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.

examples/nestjs/src/calculate.controller.ts
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
  }
}