0.2.0 • Published 2 years ago

@sabl/storage-pool v0.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

codecov

version: 0.2.0 | tag: v0.2.0 | commit: fb3bfde19

@sabl/storage-pool

storage-pool is a simple, context-aware pattern for describing connection pooling and storage transactions agnostic of the underlying storage type.

The interfaces defined in this package set consistent expectations for how storage services should expose basic pool and connection types. They are based on the connection lifecycle APIs in the go standard library database/sql package. One minor difference is the that commit and rollback methods of the Txn interface accept a context.

For more detail on the storage-pool pattern, see sabl / patterns / storage-pool.

Compatibility with @sabl/txn

This package does not depend on @sabl/txn, but the StorageTxn type defined here is compatible with the Txn interface in @sabl/txn, and StoragePool and StorageConn are compatible with the Transactable interface in @sabl/txn.

API

StorageAPI

An abstraction of all storage API types, exposing two enumeration properties that provide some clues about the type of the API instance.

export interface StorageApi {
  readonly mode: StorageMode;
  readonly kind: StorageKind | string;
}

StorageMode

Represents the basic type of the API instance: pool, connection, or transaction.

export enum StorageMode {
  pool = 1,
  conn = 2,
  txn = 3,
}

StorageKind

Extensible string enumeration describing the basic underlying storage type, such as relational, document, graph, etc. Authors may use their own values not defined here.

export enum StorageKind {
  unknown = 'unknown',
  rdb = 'relational',
  doc = 'document',
  graph = 'graph',
  keyval = 'key-value',
  widecol = 'wide-column',
}

StoragePool

interface StoragePool extends StorageApi { 
  conn(ctx: IContext): Promise<StorageConn>; 
  beginTxn(ctx: IContext, opts?: TxnOptions): Promise<StorageTxn>;
  close(): Promise<void>;
}

A pool of storage connections.

Implementations of StoragePool should return StorageMode.pool for their mode property.

conn

Retrieves a connection from the pool. The context provided may be cancelable, and if the context is canceled before a connection becomes available then conn should throw an exception. The resolved connection should already be open and ready for use.

If ctx is canceled: If a connection has already been returned, nothing happens. The cancellation applies only to the request to obtain a connection.

beginTxn

Begins a transaction on a transient connection that will be returned to the pool when the transaction completes. Implementers should respect a cancelable context and rollback the transaction if the context is canceled before the transaction is committed.

If ctx is canceled: Any ongoing operations on the transaction returned from beginTxn are immediately aborted, the transaction is rolled back, and the underlying connection is closed and returned to the pool.

close

Closes the entire pool. Pools are meant to be long-lived and concurrent-safe, so this is generally only used on graceful program termination. Should resolve when all connections have been gracefully terminated.

StorageConn

export interface StorageConn extends StorageApi {
  beginTxn(ctx: IContext, opts?: unknown): Promise<StorageTxn>;
  close(): Promise<void>;
}

An open connection to a storage provider. Maintains session state such as variables, temporary tables, and transactions. Users of a connection are expected to ensure the connection is closed when they are done with it.

Implementations of StorageConn should return StorageMode.conn for their mode property.

beginTxn

Begins a transaction on the connection. Implementers should respect a cancelable context and rollback the transaction if the context is canceled before the transaction is committed.

If ctx is canceled: Any ongoing operations on the transaction returned from beginTxn are immediately aborted and the transaction is rolled back, but the connection itself remains open.

close

Closes the connection, waiting for all ongoing operations and transactions to complete. If the connection was obtained from a pool, this should release the connection back to the pool rather than terminating the underlying connection.

StorageTxn

interface StorageTxn extends StorageApi {
  commit(): Promise<void>;
  rollback(): Promise<void>;
}

An active storage transaction.

Implementations of StorageTxn should return StorageMode.txn for their mode property.

commit

Commits and closes the transaction.

rollback

Rolls back and closes the transaction.

Concepts

Many storage clients are able to pool connections to a remote data store. Consuming code should retrieve a connection from the pool when it needs one, and promptly return the connection when the work is done, whether or not the work was successful.

These concepts are represented by the StoragePool and StorageConn interfaces.

Some storage services also support transactions. A transaction represents a series of actions whose effects either all succeed or all fail together. A transaction is represented by the StorageTxn interface.

Type-Specific CRUD APIs

Many storage client libraries will expose the same type-specific CRUD APIs on all three basic types - pool, connection, and transaction.

For example, a document store would support APIs such as insertOne, updateMany, and find:

/** Example: Common Doc store API */
interface DocStoreAPI {
  insertOne(ctx: IContext, collection: string, doc:Doc, opts): Promise<void>;
  insertMany(ctx: IContext, collection: string, docs: Doc[], opts): Promise<void>;
  find(ctx: IContext, collection: string, filter: any): Promise<Cursor>;
  ... etc ...
}

All of these APIs are inherited by a DocStorePool, DocStoreConn, and DocStoreTxn.

  • If invoked directly on a pool, a connection is automatically acquired, used, and then released as soon as the operation is complete.
  • If invoked on a connection, the connection is left open for subsequent operations
  • If invoked on a transaction, the transaction is left uncommitted for subsequent operations

The actual makeup of the common storage API differs by storage type. However, this library still defines a very simple base StorageAPI that exposes two read-only properties that allow consuming code to make basic decisions about an implementing instance without having to use fickle reflection methods such as instanceof.

Example: StackAPI

The tests of this library include a minimal but accurate example of both the interfaces and an implementation for a type-specific api, using a simple stack as the underlying 'data store'. See source for details.

// EXAMPLE, included in test/fixtures of this repo:

// StackApi is the basic stack ops: push, peek, pop
export interface StackApi extends StorageApi {
  push(ctx: IContext, val: unknown): Promise<number>;
  peek(ctx: IContext): Promise<unknown>;
  pop(ctx: IContext): Promise<unknown>;
}

// StackTxn is a composition of the basic StorageTxn
// (commit, rollback) with the StackApi
export interface StackTxn extends StorageTxn, StackApi {}

// Overrides basic Transactable so that the return
// value is a StackTxn
export interface StackTransactable extends Transactable {
  beginTxn(ctx: IContext, opts?: TxnOptions): Promise<StackTxn>;
}

// Composition that is structurally compatible with StorageConn
export interface StackConn extends StackApi, StackTransactable {
  close(): Promise<void>;
}

// Composition that is structurally compatible with StoragePool
export interface StackPool extends StackApi, StackTransactable {
  conn(ctx: IContext): Promise<StackConn>;
  close(): Promise<void>;
}