Skip to content

io7m-com/darco

Repository files navigation

darco

Maven Central Maven Central (snapshot) Codecov Java Version

com.io7m.darco

JVM Platform Status
OpenJDK (Temurin) Current Linux Build (OpenJDK (Temurin) Current, Linux)
OpenJDK (Temurin) LTS Linux Build (OpenJDK (Temurin) LTS, Linux)
OpenJDK (Temurin) Current Windows Build (OpenJDK (Temurin) Current, Windows)
OpenJDK (Temurin) LTS Windows Build (OpenJDK (Temurin) LTS, Windows)

darco

The darco package provides a minimalist, opinionated API for database access.

Features

  • Versioned schema upgrades provided by trasco.
  • Pluggable query support.
  • Instrumented with OpenTelemetry.
  • PostgreSQL support.
  • SQLite support.
  • Written in pure Java 21.
  • OSGi ready
  • JPMS ready
  • ISC license
  • High-coverage automated test suite

Motivation

Many io7m packages talk to various databases. For example:

  • idstore uses PostgreSQL as the underlying persistent data store.
  • cardant uses PostgreSQL as the underlying persistent data store.
  • northpike uses PostgreSQL as the underlying persistent data store.
  • certusine uses SQLite as the underlying persistent data store.
  • looseleaf uses SQLite as the underlying persistent data store.
  • ...

All of the listed projects abstracted the database behind a simplified set of interfaces to allow for easier unit testing, and to allow for migrating to different databases without having to completely rewrite all of the application code. This meant that each one of those projects had to implement its own set of nearly-identical interfaces and initialization code boilerplate.

In much the same manner as the hibiscus API attempts to provide a common interface around RPC clients, and the anethum API attempts to provide a common interface around parsers and serializers, the darco package attempts to provide a common set of interfaces and abstract classes to minimize the amount of essentially duplicated code between projects that talk to databases.

Building

$ mvn clean verify

Usage

The com.io7m.darco.api package provides the basic interfaces that abstract over a relational database. Projects are expected to extend various provided abstract classes, and implement various interfaces to provide their own database abstractions. The package exposes a database as small set of core types listed in the following sections.

DDatabaseFactoryType databases;
DDatabaseConfigurationType configuration;

try (DDatabaseType database = databases.open(configuration, () -> {})) {
  try (DDatabaseConnectionType connection = database.openConnection()) {
    try (DDatabaseTransactionType transaction = connection.openTransaction()) {
      DDatabaseQueryType<String, DDatabaseUnit> query =
        transaction.query(SomeCustomQueryType.class);

      query.execute("Hello!");
      query.execute("Goodbye!");
      transaction.commit();
    }
  }
}

DDatabaseFactoryType

The DDatabaseFactoryType interface represents objects that provide database instances. When a DDatabaseFactoryType is provided with a DDatabaseConfigurationType value, it yields a DDatabaseType instance.

DDatabaseConfigurationType

The DDatabaseConfigurationType interface represents the basic configuration properties required to open and/or connect to a database.

DDatabaseType

The DDatabaseType interface represents an open database. To interact with the database, callers must call the openConnection() method to obtain a DDatabaseConnectionType instance. In a typical request/response server application, the application would obtain a new connection for each incoming client request, and close() the connection after servicing the request.

DDatabaseConnectionType

The DDatabaseConnectionType interface represents an open connection to a database. In order to perform database queries using the connection, applications must create transactions within which to execute queries by calling the openTransaction() method.

DDatabaseTransactionType

The DDatabaseTransactionType interface represents a database transaction. Applications perform work in transactions by requesting instances of queries from the transaction. Transactions must be explicitly committed, otherwise the work performed by queries inside the transaction is implicitly rolled back.

DDatabaseQueryType

The DDatabaseQueryType interface represents a single query that can be executed within a transaction. A query typically abstracts over one or more SQL statements. Queries are strongly typed and are represented as functions taking parameters of type P and returning results of type R.

Query implementations are provided by instances of the DDatabaseQueryProviderType interface. Typically, a set of DDatabaseQueryProviderType instances are registered using ServiceLoader.

SQLite

Applications wishing to provide an abstraction over the SQLite database should extend the abstract classes exposed by the com.io7m.darco.sqlite module.

PostgreSQL

Applications wishing to provide an abstraction over the PostgreSQL database should extend the abstract classes exposed by the com.io7m.darco.postgres module.

Examples

The com.io7m.darco.examples module provides example implementations of databases.