Skip to main content

Create custom providers

Booster provides an infrastructure layer out of the box with sensible defaults that you can use for rapid development, but if you want to have control over the infrastructure deployed to your cloud provider or start from scratch with a fully customized infrastructure implementation. Booster layered architecture allows you to do so by implementing a provider package.

How do Booster cloud providers work?

Booster providers require the implementation of two specific interfaces, often delivered as separate packages to avoid including dependencies required at deployment time as part of your application package:

  • Infrastructure: This interface is used during deployment to create, destroy and configure all the infrastructure necessary to run the application.
  • Runtime: This inteface implements all the interaction between Booster framework and the deployed infrastructure in runtime.

architecture

The infrastructure interface in detail

The provider infrastructure interface by convention is implemented in a package ending with -infrastructure name like the framework-provider-aws-infrastructure.
As it has been commented, this package includes all the necessary to deploy and configure cloud elements for running your application. For instance in the case of AWS, this package is in charge of deploy the DynamoDB for your event store, create all the lambdas, and configure all the API gateway configuration for your application.

The infrastructure package interface is composed of four methods:

    export interface ProviderInfrastructure {
deploy?: (config: BoosterConfig) => Promise<void>
nuke?: (config: BoosterConfig) => Promise<void>
start?: (config: BoosterConfig, port: number) => Promise<void>
synth?: (config: BoosterConfig) => Promise<void>
}
  • deploy: This method is called during the deployment by the CLI and it should be in charge to deploy all the neccesary resource for your application and rockets.
  • nuke: This is method is charge of destroy all generated resources during the deploy and it is called during the nuke process.
  • start: This method is used when the provider implements a server that needs to be started (i.e. the local provider)
  • synth: This method allows you to export the infrastructure to a file (for instance, if you use the Terraform CDK, you can export the script here to run it using conventional terraform tools)

The infrastructure interface just defines an adapter for Booster so the framework knows how to start any of the described processes, but you can use any Infrastructure as Code tool that has a Typescript DSL (CDK) or even call other CLI tools or scripts if you rather maintain it using a different technology.

The runtime interface in detail

The other key aspect during the implementation of a provider is the runtime package. This package is in charge of the interaction between Booster framework and all deployed resources when the application is running. For instance, this package has the responsability to store data in the event store, performs the data projections, etc...

The runtime interface (ProviderLibrary) is divided in seven sections:

Events

This section is in charge of all operations related to events. the methods of this section are the following:

export interface ProviderEventsLibrary {
rawToEnvelopes(rawEvents: unknown): Array<EventEnvelope>
forEntitySince(config: BoosterConfig, entityTypeName: string, entityID: UUID, since?: string): Promise<Array<EventEnvelope>>
latestEntitySnapshot(config: BoosterConfig, entityTypeName: string, entityID: UUID): Promise<EventEnvelope | null>
search(config: BoosterConfig, parameters: EventSearchParameters): Promise<Array<EventSearchResponse>>
store(eventEnvelopes: Array<EventEnvelope>, config: BoosterConfig): Promise<void>
searchEntitiesIDs(config: BoosterConfig, limit: number, afterCursor: Record<string, string> | undefined, entityTypeName: string): Promise<PaginatedEntitiesIdsResult>
}
  • rawToEnvelopes: Inside the framework all user application data is processed encapsulated in an envelope object. This particular function performs the transformation from the used database data into a Booster framework envelope object.
  • forEntitySince: This method have to returns all the events associated with an specific entity.
  • latestEntitySnapshot: With this method the framework should be able to obtains the latest snapshot for an specific entity.
  • search: This method receives a query and it should perform it in the database used by the provider and return the result.
  • store: This method is used to store new events in the database.
  • searchEntitiesIDs: This method is used for implementing the pagination in searches.

Read Models

This section of the interface provides to the framework the ability to interact with the database to manage read models thanks to the following methods:

export interface ProviderReadModelsLibrary {
rawToEnvelopes(config: BoosterConfig, rawEvents: unknown): Promise<Array<ReadModelEnvelope>>
fetch(config: BoosterConfig, readModelName: string, readModelID: UUID, sequenceKey?: SequenceKey): Promise<ReadOnlyNonEmptyArray<ReadModelInterface>>
search<TReadModel extends ReadModelInterface>(config: BoosterConfig, entityTypeName: string, filters: FilterFor<unknown>, sortBy?: SortFor<unknown>, limit?: number, afterCursor?: unknown, paginatedVersion?: boolean): Promise<Array<TReadModel> | ReadModelListResult<TReadModel>>
store(config: BoosterConfig, readModelName: string, readModel: ReadModelInterface, expectedCurrentVersion?: number): Promise<unknown>
delete(config: BoosterConfig, readModelName: string, readModel: ReadModelInterface | undefined): Promise<any>
subscribe(config: BoosterConfig, subscriptionEnvelope: SubscriptionEnvelope): Promise<void>
fetchSubscriptions(config: BoosterConfig, subscriptionName: string): Promise<Array<SubscriptionEnvelope>>
deleteSubscription(config: BoosterConfig, connectionID: string, subscriptionID: string): Promise<void>
deleteAllSubscriptions(config: BoosterConfig, connectionID: string): Promise<void>
  • rawToEnvelopes: This method is used to transform all database data into read models envelopes.
  • fetch: Fetch a specific read model from the database.
  • search: This method receives a search query and it should return the read model search result.
  • store: Save a new read model projection on the database.
  • delete: Delete a read model from the database.
  • subscribe: This method is used to susbcribe a client to an specific read model.
  • fetchSubscriptions: Get the list of all clients subscribed to a specific read model.
  • deleteSubscription: Delete a specific read model subscription.
  • deleteAllSubscriptions: Delete all subscription for a specific read model.

GraphQL

This section of the API provides all necessary to receive and return GraphQL query from client side and create the return for requests:

export interface ProviderGraphQLLibrary {
rawToEnvelope(config: BoosterConfig, rawGraphQLRequest: unknown): Promise<GraphQLRequestEnvelope | GraphQLRequestEnvelopeError>
handleResult(result?: unknown, headers?: Record<string, string>): Promise<unknown>
}
  • rawToEnvelope: This method receives the request from the client with the GraphQL query and it should return the envelope object for the GraphQL query
  • handleResult This method receives the GraphQL results and it should return the response object for the client.

API responses

General API response management:

export interface ProviderAPIHandling {
requestSucceeded(body?: unknown, headers?: Record<string, number | string | ReadonlyArray<string>>): Promise<unknown>
requestFailed(error: Error): Promise<unknown>
}
  • requestSucceeded: This is a general method for processing sucess responses.
  • requestFailed: This is a general method for processing error responses.

Connections

This section of the API is in charge of the connection management for subscription at API gateway level:

export interface ProviderConnectionsLibrary {
storeData(config: BoosterConfig, connectionID: string, data: ConnectionDataEnvelope): Promise<void>
fetchData(config: BoosterConfig, connectionID: string): Promise<ConnectionDataEnvelope | undefined>
deleteData(config: BoosterConfig, connectionID: string): Promise<void>
sendMessage(config: BoosterConfig, connectionID: string, data: unknown): Promise<void>
}
  • storeData: This method receives all the information about the incoming connection and it should store the data on a database.
  • fetchData: Fetch the specific client connection information from the database.
  • deleteData: Delete all the information about a specific client connection.
  • sendMessage: Send a message to a specific client. This method get the message and destination as parameters and it should be able to fetch the connection information from the database and send the provided data to the client.

Scheduled

Finally, this section of the API is related to scheduled commands:

export interface ScheduledCommandsLibrary {
rawToEnvelope(config: BoosterConfig, rawMessage: unknown): Promise<ScheduledCommandEnvelope>
}
  • rawToEnvelope: as in other sections, this method is in charge to transform the scheduled command into a framework envelope.

Tips for developing custom providers

  • As a starting point, check the implementation of other providers to check how evertyhing is implemented.
  • Start the provider implementation by the infrastructure package because you will get all the infrastructure deployed and later the work with the runtime API will be easier.
  • If you need support during the development remember that you can have access to our Discord where some community members will can help you.