Introducing Drivine

Posted on March 01, 2020 by Jasper Blues in Drivine.

I’m happy to share that the Drivine 1.x release train is in flight!

Drivine is a database client for Node.js and TypeScript, with first-class support for graph databases, like Neo4j.

With Drivine, you can manage multiple databases, simultaneously, if you wish. The library is designed to scale to hundreds or thousands of transactions per second, and it allows you to meet these goals without compromising architectural integrity.

Let’s explore some key features of the library.

Database Registration

Databases or user profiles for a given database are registered on startup or at runtime, with a registry.

@Module({
    imports: [
        DrivineModule.withOptions(<DrivineModuleOptions>{
            connectionProviders: [
                DatabaseRegistry.buildOrResolveFromEnv('BPM_MAIN'),
                DatabaseRegistry.buildOrResolveFromEnv('CUSTOMER_1'),
                DatabaseRegistry.buildOrResolveFromEnv('CUSTOMER_2')
            ]
        }),
        HealthModule,
        CustomerModule
    ],
    controllers: [],
    providers: []
})
export class AppModule {}

Distributed Transactions

One of the features that differentiates the library is its support for distributed transactions - even across databases of different types. Transactions boundaries are specified using a TypeScript decorator.


@Injectable()
export class RouteRepository {
    constructor(
        @InjectPersistenceManager('BPM_MAIN')
        readonly persistenceManager: PersistenceManager,
        @InjectCypher('@/traffic/routesBetween')
        readonly routesBetween: CypherStatement
    ) {}

    @Transactional()
    async findFastestBetween(start: string, destination: string):
        Promise<Route> {

        return this.persistenceManager.getOne(
            new QuerySpecification<Route>()
                .withStatement(this.routesBetween)
                .bind([start, destination])
                .limit(1)
                .transform(Route)
        );
    }
}

Data Integrity & Security:

Drvine’s distributed transactions are being used in a business process management product, where transactions must span between a management database and client databases. The application is multi-tenanted, so each client runs in their own separate database.

Another important usage is in B2B support where customers have limited access to another customer’s database for process hand-off between two enterprises.

Streaming

Another key feature of the library is streaming, which can be acheived either useing Node.js for-await syntax or using pipes. To achieve streaming, we first open a Cursor<T>:

@Transactional()
async asyncRoutesBetween(start: string, destination: string):
    Promise<Cursor<Route>> {

    return this.persistenceManager.openCursor(
        new CursorSpecification<Route>()
            .withStatement(this.routesBetween)
            .bind([start, destination])
            .batchSize(5)
            .transform(Route)
    );
}

For-await

With for-wait items will be fetched as needed.

const cursor = await repo.asyncRoutesBetween('Cavite Island', 'NYC');
for await (const item of cursor) {
    expect(item.travelTime).toBeGreaterThan(0);
    expect(item.metros.length).toBeGreaterThan(0);
    expect(item).toBeInstanceOf(Route);
}

Pipes

If the destination for streamed data is an output stream, it is best to use pipes, in order to avoid back-pressure.

cursor.asStream({ transform: (route: Route) => route.toString() }).pipe(fileStream);
await StreamUtils.untilClosed(fileStream);

Getting Started

If you would like to know more about Drivine, including how to get started, please visit the website. And of course, if you're using Drivine already, we'd love to hear about it!

Comments

Get in touch

We'd love to hear from you! Feel free to reach out on any of the channels below.

801 Glenferrie Road Hawthorn, VIC