Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(doc): update the doc about extending sdk #124

Merged
merged 1 commit into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/resource-class-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 92 additions & 20 deletions docs/dev_guide/extend-sdk.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ If you encounter any unclear concepts or unexplained terms in this document, ple
- Define two types of functional interfaces in the Client SDK:
- An interface for defining runtime functionality methods, called the Client Interface.
- An interface for defining infrastructure-related methods, called the Infra Interface.
- An interface that includes attributes generated at compile-time and accessed at runtime, referred to as a Prop Interface.
- Define a resource operation interface in the Client SDK to expose the functionality methods for the resource.
- Adding a new implementation for the resource type
- Create a class implementation for the Client Interface in the Client SDK and bind it through the `buildClient` method of the resource type.
Expand All @@ -29,7 +30,7 @@ The methods defined in the Client Interface are the runtime functional methods t

```typescript
// The client interface is used to define the methods for accessing resources that are used during runtime.
export interface QueueClient {
export interface IQueueClientApi extends base.IResourceClientApi {
push(msg: string): Promise<void>;
}
```
Expand All @@ -41,14 +42,12 @@ The methods defined in the Infra Interface are used to build resource associatio
As a message queue, the Queue usually allows creating a subscriber to consume the messages published in the Queue. The method for creating a subscriber is called `subscribe`. The `subscribe` method takes an `EventHandler` type object as a parameter. The `EventHandler` type is a function type interface that inherits the `base.FnResource` interface, indicating that the `EventHandler` type is a FaaS resource type.

```typescript
import { FnResource } from "@plutolang/base";

// The infra interface is used to define the methods for accessing resources that are used during compilation.
export interface QueueInfra {
export interface IQueueInfraApi extends base.IResourceInfraApi {
subscribe(fn: EventHandler): void;
}

export interface EventHandler extends FnResource {
export interface EventHandler extends base.FnResource {
(evt: CloudEvent): Promise<void>;
}

Expand All @@ -58,7 +57,15 @@ export interface CloudEvent {
}
```

### Define the resource operation interface exposed to users
### Define the Prop Interface

The property interface defines a set of getter methods. The values corresponding to these methods cannot be obtained solely based on the data provided by the user. For instance, the URL of a router in apigateway can only be known after deployment. Currently, Queue does not have such requirements, hence it is empty.

```typescript
export interface IQueueCapturedProps extends base.IResourceCapturedProps {}
```

### Define the Resource Operation Interface Exposed to Users

The resource operation interface exposed to users consists of a pair of classes and interfaces with the same name. The interface inherits both the client interface and the infrastructure interface, while the class only defines the constructor and a static `buildClient` method. This leverages the TypeScript feature of type merging, allowing the class to provide sufficient hints to developers even though it does not implement the interface methods.

Expand All @@ -68,7 +75,10 @@ Here, the resource class should be treated as an equivalent of an abstract class
import { Resource, runtime } from "@plutolang/base";
import { aws, k8s } from "./clients";

export class Queue implements Resource {
export type IQueueClient = IQueueCapturedProps & IQueueClientApi;
export type IQueueInfra = IQueueCapturedProps & IQueueInfraApi;

export class Queue {
constructor(name: string, opts?: QueueOptions) {
name;
opts;
Expand All @@ -77,7 +87,7 @@ export class Queue implements Resource {
);
}

public static buildClient(name: string, opts?: QueueClientOptions): QueueClient {
public static buildClient(name: string, opts?: QueueClientOptions): IQueueClient {
const rtType = process.env["RUNTIME_TYPE"];
switch (rtType) {
case runtime.Type.K8s:
Expand All @@ -88,7 +98,7 @@ export class Queue implements Resource {
}
}

export interface Queue extends QueueInfra, QueueClient, Resource {}
export interface Queue extends IQueueClient, IQueueInfra, IResource {}

export interface QueueInfraOptions {}
export interface QueueClientOptions {}
Expand All @@ -101,18 +111,18 @@ export interface QueueOptions extends QueueInfraOptions, QueueClientOptions {}

In the `src/clients/aws` directory of `@plutolang/pluto`, create an `snsQueue.ts` file. The file and the class it contains are usually named after the component and the type.

In this file, the `SNSQueue` class implements the `QueueClient` interface using the AWS SDK. When calling the `PublishCommand` in the aws-sdk, the ARN of the SNS topic needs to be specified. Here, the ARN is constructed by concatenating the required parameters obtained from the environment variables, which are set in the aws `runtime.ts` of `@plutolang/pluto-infra`.
In this file, the `SNSQueue` class implements the `IQueueClient` interface using the AWS SDK. When calling the `PublishCommand` in the aws-sdk, the ARN of the SNS topic needs to be specified. Here, the ARN is constructed by concatenating the required parameters obtained from the environment variables, which are set in the aws `runtime.ts` of `@plutolang/pluto-infra`.

_Currently, there is no effective solution on how to transfer the information generated during compilation to the runtime for effective use._

```typescript
import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
import { CloudEvent, QueueClient, QueueClientOptions } from "../../queue";
import { CloudEvent, IQueueClient, QueueClientOptions } from "../../queue";

/**
* Implementation of Queue using AWS SNS.
*/
export class SNSQueue implements QueueClient {
export class SNSQueue implements IQueueClient {
private topicName: string;
private client: SNSClient;

Expand Down Expand Up @@ -155,7 +165,7 @@ After implementing the `SNSQueue` class, it needs to be created at runtime based

```typescript
...
public static buildClient(name: string, opts?: QueueClientOptions): QueueClient {
public static buildClient(name: string, opts?: QueueClientOptions): IQueueClient {
const rtType = process.env["RUNTIME_TYPE"];
switch (rtType) {
case runtime.Type.AWS:
Expand All @@ -165,28 +175,83 @@ public static buildClient(name: string, opts?: QueueClientOptions): QueueClient
default:
throw new Error(`not support this runtime '${rtType}'`);
}
}
...
```

### Create an Abstract Base Class for the Infrastructure Implementation Class

In the `src/` directory of `@plutolang/pluto-infra`, create a file named `queue.ts`. In this file, define the interfaces that the base class needs to implement. Also, define an abstract class for instantiating specific implementation classes based on platform and engine. The example below uses lazy loading to import corresponding specific instances, reducing library load time.

When implementing, it's important to note that the parameters of the constructor for implementation classes and Queue's static method `createInstance` should be consistent with those of Client's constructor.

```typescript
import { engine, runtime, utils } from "@plutolang/base";
import { IQueueInfra QueueOptions } from "@plutolang/pluto";
import { ImplClassMap } from "./utils";

// Construct a type for a class constructor. The key point is that the parameters of the constructor
// must be consistent with the client class of this resource type. Use this type to ensure that
// all implementation classes have the correct and same constructor signature.
type QueueInfraImplClass = new (name: string, options?: QueueOptions) => IQueueInfra;

// Construct a map that contains all the implementation classes for this resource type.
// The final selection will be determined at runtime, and the class will be imported lazily.
const implClassMap = new ImplClassMap<IQueueInfra, QueueInfraImplClass>({
[engine.Type.pulumi]: {
[runtime.Type.AWS]: async () => (await import("./aws")).SNSQueue,
[runtime.Type.K8s]: async () => (await import("./k8s")).RedisQueue,
},
});

/**
* This is a factory class that provides an interface to create instances of this resource type
* based on the target platform and engine.
*/
export abstract class Queue {
/**
* Asynchronously creates an instance of the queue infrastructure class. The parameters of this function
* must be consistent with the constructor of both the client class and infrastructure class associated
* with this resource type.
*/
public static async createInstance(name: string, options?: QueueOptions): Promise<IQueueInfra> {
// TODO: ensure that the resource implementation class for the simulator has identical methods as those for the cloud.
if (
utils.currentPlatformType() === runtime.Type.Simulator &&
utils.currentEngineType() === engine.Type.simulator
) {
return new (await import("./simulator")).SimQueue(name, options) as any;
}

return implClassMap.createInstanceOrThrow(
utils.currentPlatformType(),
utils.currentEngineType(),
name,
options
);
}
}
```

### Create an Infrastructure Implementation Class

In the `src/aws` directory of `@plutolang/pluto-infra`, create an `snsQueue.ts` file. The file and the class it contains are usually named after the component and the type. In this file, the `SNSQueue` class needs to implement the `ResourceInfra` and `QueueInfra` interfaces. Typically, the creation process of the main components is defined in the constructor, and the association with other resources is built in other methods. Note that the operation names in `getPermission` should correspond to the functions in the client interface.
In the `src/aws` directory of `@plutolang/pluto-infra`, create an `snsQueue.ts` file. The file and the class it contains are usually named after the component and the type. In this file, the `SNSQueue` class needs to implement the `ResourceInfra` and `IQueueInfra` interfaces. Typically, the creation process of the main components is defined in the constructor, and the association with other resources is built in other methods. Note that the operation names in `getPermission` should correspond to the functions in the client interface.

Currently, Pluto supports implementation based on Pulumi and will support more IaC tools in the future.

```typescript
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";
import { Resource, ResourceInfra } from "@plutolang/base";
import { QueueInfra, QueueInfraOptions } from "@plutolang/pluto/dist/queue";
import { IQueueInfra, QueueInfraOptions } from "@plutolang/pluto";
import { Lambda } from "./lambda";
import { Permission } from "./permission";

export enum SNSOps {
PUSH = "push",
}

export class SNSQueue extends pulumi.ComponentResource implements ResourceInfra, QueueInfra {
export class SNSQueue extends pulumi.ComponentResource implements ResourceInfra, IQueueInfra {
readonly name: string;
public readonly topic: aws.sns.Topic;

Expand Down Expand Up @@ -257,10 +322,17 @@ export class SNSQueue extends pulumi.ComponentResource implements ResourceInfra,
}
```

After implementing the `SNSQueue` class, it needs to be registered to the registry in order to instantiate the corresponding implementation class for the target platform during deployment. In the `src/aws/index.ts` of `@plutolang/pluto-infra`, export this class and add the following statement in the `register` method of `src/index.ts` to register it:
After implementing the `SNSQueue` class, it needs to be registered to the abstract base class of `Queue`. In the `src/queue.ts` of `@plutolang/pluto-infra`, add the following statement to the `implClassMap` variable:

```typescript
reg.register(runtime.Type.AWS, engine.Type.pulumi, Queue, aws.SNSQueue);
the registry in order to instantiate the corresponding implementation class for the target platform during deployment. In the `src/aws/index.ts` of `@plutolang/pluto-infra`, export this class and add the following statement in the `register` method of `src/index.ts` to register it:

```typescript {3}
const implClassMap = new ImplClassMap<IQueueInfra, QueueInfraImplClass>({
[engine.Type.pulumi]: {
[runtime.Type.AWS]: async () => (await import("./aws")).SNSQueue,
[runtime.Type.K8s]: async () => (await import("./k8s")).RedisQueue,
},
});
```

With this, the extension of the BaaS resource type is completed.
Expand All @@ -271,6 +343,6 @@ Not all resources have both the Client Interface and the Infra Interface.

For example, the Router resource only has the Infra Interface and does not have the Client Interface, meaning the Router type does not have any functional methods for the compute module to call during runtime.

The KVStore resource only has the Client Interface and does not have the Infra Interface, indicating that the Router type currently does not have the need to establish triggering associations with other resources.
The KVStore resource only has the Client Interface and does not have the Infra Interface, indicating that the KVStore type currently does not have the need to establish triggering associations with other resources.

It is important to note that whether there is an Infra Interface or not, if it's a type of cloud resource, there needs to be an infrastructure implementation class that completes the resource creation in its constructor.
Loading