Co-authored-by: Monta <monta@HP-ProBook-445-14-inch-G9-Notebook-PC-505aadfc.localdomain>
7.1 KiB
description
| description |
|---|
| If you are here, you probably have a very great idea for Twake Drive, like adding a brand new feature into Twake Drive, maybe a coffee maker service ? ☕️ |
Create a new service
::: info Please ensure you read the Start working into a service before :::
To create a new component, a new folder must be created under the src/services one and an index.ts file must export the a class. This class will be instantiated by the platform and will be linked to the required services automatically.
In order to illustrate how to create a component, let's create a fake Notification service.
- Create the folder
src/services/notification - Create an
index.tsfile which exports aNotificationServiceclass
// File src/services/notification/index.ts
import { TdriveService } from "../../core/platform/framework";
import NotificationServiceAPI from "./api.ts";
export default class NotificationService extends TdriveService<NotificationServiceAPI> {
version = "1";
name = "notification";
service: NotificationServiceAPI;
api(): NotificationServiceAPI {
return this.service;
}
}
-
Our
NotificationServiceclass extends the genericTdriveServiceclass and we defined theNotificationServiceAPIas its generic type parameter. It means that in the platform, the other components will be able to retrieve the component from its name and then consume the API defined in theNotificationServiceAPIinterface and exposed by theapimethod.We need to create this
NotificationServiceAPIinterface which must extend theTdriveServiceProviderfrom the platform like:
// File src/services/notification/api.ts
import { TdriveServiceProvider } from "../../core/platform/framework/api";
export default interface NotificationServiceAPI extends TdriveServiceProvider {
/**
* Send a message to a list of recipients
*/
send(message: string, recipients: string[]): Promise<string>;
}
- Now that the interfaces are defined, we need to create the
NotificationServiceAPIimplementation (this is a dummy implementation which does nothing but illustrates the process):
// File src/services/notification/services/api.ts
import NotificationServiceAPI from "../api";
export class NotificationServiceImpl implements NotificationServiceAPI {
version = "1";
async send(message: string, recipients: string[]): Promise<string> {
return Promise.resolve(`${message} sent`);
}
}
NotificationServiceImplnow needs to be instanciated from theNotificationServiceclass since this is where we choose to keep its reference and expose it. There are several places which can be used to instanciate it, in the constructor itself, or in one of theTdriveServicelifecycle hooks. TheTdriveServiceabstract class has several lifecycle hooks which can be extended by the service implementation for customization pusposes:public async doInit(): Promise<this>;Customize theinitstep of the component. This is generally the place where services are instanciated. From this step, you can retrieve services consumed by the current component which have been already initialized by the platform.public async doStart(): Promise<this>;Customize thestartstep of the component. You have access to all other services which are already started.
// File src/services/notification/index.ts
import { TdriveService } from "../../core/platform/framework";
import NotificationServiceAPI from "./api.ts";
import NotificationServiceImpl from "./services/api.ts";
export default class NotificationService extends TdriveService<NotificationServiceAPI> {
version = "1";
name = "notification";
service: NotificationServiceAPI;
api(): NotificationServiceAPI {
return this.service;
}
public async doInit(): Promise<this> {
this.service = new NotificationServiceImpl();
return this;
}
}
- Now that the service is fully created, we can consume it from any other service in the platform. To do this, we rely on Typescript decorators to define the links between components. For example, let's say that the a
MessageServiceneeds to call theNotificationServiceAPI, we can create the link with the help of the@Consumesdecorator and get a reference to theNotificationServiceAPIby calling thegetProvideron the component context like:
import { TdriveService, Consumes } from "../../core/platform/framework";
import MessageServiceAPI from "./providapier";
import NotificationServiceAPI from "../notification/api";
@Consumes(["notification"])
export default class MessageService extends TdriveService<MessageServiceAPI> {
public async doInit(): Promise<this> {
const notificationService = this.context.getProvider<NotificationServiceAPI>("notification");
// You can not call anything defined in the NotificationServiceAPI interface from here or from inner services by passing down the reference to notificationService.
}
}
Configuration
The platform and services configuration is defined in the config/default.json file. It uses node-config under the hood and to configuration file inheritence is supported in the platform.
The list of services to start is defined in the services array like:
{
"services": ["auth", "user", "channels", "webserver", "websocket", "database", "realtime"]
}
```<!-- TODO[NOT UP TO DATE] -->
Then each service can have its own configuration block which is accessible from its service name i.e. `websocket` service configuration is defined in the `websocket` element like:
```javascript
{
"services": ["auth", "user", "channels", "webserver", "websocket", "orm"],
"websocket": {
"path": "/socket",
"adapters": {
"types": [],
"redis": {
"host": "redis",
"port": 6379
}
}
}
}
On the component class side, the configuration object is directly accessible from the configuration property like:
export default class WebSocket extends TdriveService<WebSocketAPI> {
async doInit(): Promise<this> {
// get the "path" value, defaults to "/socket" if not defined
const path = this.configuration.get < string > ("path", "/socket");
// The "get" method is generic and can accept custom types like
const adapters =
this.configuration.get < AdaptersConfiguration > "adapters";
}
}
interface AdaptersConfiguration {
types: Array<string>;
redis: SocketIORedis.SocketIORedisOptions;
}
::: info After creating a new service, you can add controllers, business services and entities, go back to the What is a service section :::
Create a new technical service
Now you are bringing things a step further, you are going to add new core services in Twake Drive, like for instance a new database connector or encryption system.
Creating a new core service is as easy as creating a functional service. But it must be in src/core/platform/services .
You can read the complete list of existing technical services here) .