Service — The Container
Custom Service Class
Extend the base class for gateway and adapter patterns
In general, a service should not contain business logic. Its role is to act as a logical container for commands and subscriptions.
The primary reason for this separation is to prevent handlers from becoming tightly coupled to the service and its state. Tight coupling increases complexity, makes unit testing harder, and reduces the flexibility to deploy individual functions as FaaS.
Whenever possible, use resources instead of custom classes. Resources offer better decoupling, a higher level of abstraction, and improved scalability.
When a custom class is justified
A custom service class makes sense when your service needs to act as an adapter or gateway to a third-party solution, and there is no viable infrastructure-level alternative. In these cases, be aware that the service will run as a continuously active instance and cannot be deployed as FaaS.
Example: third-party gateway
import {
CustomMessage,
EBMessageAddress,
EBMessageType,
getNewTraceId,
Service,
} from '@purista/core'
import { customConnect, CustomProvider } from 'custom-provider'
import { UserServiceV1Config } from './userServiceConfig'
export class CustomUserClass extends Service<UserServiceV1Config> {
private customProvider?: CustomProvider
async start() {
await super.start()
this.customProvider = await customConnect(this.config)
this.customProvider.on('data', async (data: unknown) => {
const sender: EBMessageAddress = {
serviceName: this.info.serviceName,
serviceVersion: this.info.serviceVersion,
serviceTarget: '',
}
const message = Object.freeze({
messageType: EBMessageType.CustomMessage,
traceId: getNewTraceId(),
contentType: 'application/json',
contentEncoding: 'utf-8',
sender,
eventName: 'myCustomEvent',
payload: data,
})
await this.eventBridge.emitMessage(message)
})
}
async destroy() {
await this.customProvider?.close()
await super.destroy()
}
}
Register the custom class in the service builder:
import { ServiceBuilder, ServiceInfoType } from '@purista/core'
import { CustomUserClass } from './CustomUserClass'
export const userV1ServiceBuilder = new ServiceBuilder(userServiceInfo)
.setConfigSchema(userServiceV1ConfigSchema)
.setCustomClass(CustomUserClass)
Bidirectional gateway
To push data from PURISTA back to the third-party provider, create a command or subscription:
.setCommandFunction(async function (_context, payload) {
const response = await this.customProvider.send(payload)
return { response }
})
If you need a custom service class, try to avoid adding many subscriptions or commands here. Reduce it to the bare minimum, and consider an additional service for handlers that do not directly need the custom functionality.