Skip to main content
Version: 2.1.x

Architecture Overview

DB plugin is a flexible, extensible database abstraction layer that provides a unified interface for working with multiple database backends. It uses a data mapper pattern to separate domain objects from persistence logic, enabling powerful features like multi-database routing, caching, and real-time replication.

Core Components​

Database & AbstractDatabase​

The Database interface is the central abstraction that defines all database operations. It provides methods for:

  • Reading: Query execution, counting, pagination
  • Writing: Saving, deleting, indexing
  • Transactions: Begin/commit/rollback operations
  • Configuration: Connection management, naming, utilities

Multiple database implementations can be registered and accessed by name, allowing your application to work with different backends simultaneously.

State​

State is the core data container that holds an object's field values. Think of it as a flexible map structure that:

  • Stores raw field values (primitives, collections, references)
  • Manages resolved references (lazy-loaded related objects)
  • Tracks validation errors
  • Maintains object lifecycle state (new, saved, deleted)
  • Provides path-based field access (author/name)

The State layer separates data storage from business logic, enabling powerful features like modifications and polymorphism.

Record & Recordable​

Recordable is the minimal interface for database-backed objects:

1
public interface Recordable {
2
State getState();
3
void setState(State state);
4
String getLabel();
5
<T> T as(Class<T> targetClass);
6
}

Record is the abstract base class that most domain objects extend. It provides:

  • Lifecycle hooks (beforeSave, afterSave, beforeDelete, afterDelete)
  • Validation hooks (onValidate, afterValidate)
  • Convenience methods for save/delete operations
  • Integration with the State layer

Query​

The Query class provides a fluent, type-safe API for building and executing queries. It's inspired by Apple's Cocoa Predicates and LINQ:

1
List<Article> articles = Query.from(Article.class)
2
.where("author/name = ?", "John Doe")
3
.and("publishDate > ?", cutoffDate)
4
.sortDescending("publishDate")
5
.select(0, 10);

Queries support predicates, sorting, pagination, grouping, and various execution modes (streaming, counting, etc.).

ObjectType​

ObjectType represents metadata about your domain types:

  • Field definitions (name, type, validation rules)
  • Index configurations
  • Type hierarchy and relationships
  • Groups for polymorphic queries
  • Source database mapping

Types are discovered and initialized at startup through the DatabaseEnvironment.

ObjectField​

ObjectField describes individual fields within a type:

  • Field name and Java type
  • Indexing configuration
  • Validation rules and annotations
  • Embedded/denormalized flags
  • Unique constraints

Architecture Patterns​

Data Mapper Pattern​

DB plugin uses the data mapper pattern to keep domain objects independent of persistence logic:

This separation allows:

  • Domain objects to focus on business logic
  • Transparent switching between database implementations
  • Testing without database dependencies
  • Complex persistence strategies (caching, replication, federation)

Composition over Inheritance (Modifications)​

Modifications enable aspect-oriented composition without complex inheritance hierarchies:

Multiple objects can link to the same State, each providing different views and behaviors:

1
Article article = state.as(Article.class);
2
SEOData seo = state.as(SEOData.class); // Modification
3
SocialData social = state.as(SocialData.class); // Another modification

This pattern allows:

  • Cross-cutting concerns to be cleanly separated
  • Dynamic behavior composition at runtime
  • Multiple aspects on the same object without inheritance conflicts

Lazy Loading (Reference Resolution)​

References to other objects are stored as lightweight stubs and resolved on-demand:

Benefits:

  • Reduces initial query overhead
  • Batch resolution for N+1 query prevention
  • Configurable resolution depth
  • Support for reference-only queries

Transaction Management​

Transactions use a depth-based nesting model with validation phases:

Features:

  • Nested transaction support
  • Write buffering and batching
  • Pre-write validation
  • Trigger firing after commit
  • Automatic retry on recoverable errors

Database Implementations​

DB plugin supports multiple backend implementations:

AbstractSqlDatabase (platform-sql)​

Stores objects in relational databases (MySQL, PostgreSQL, Oracle, SQL Server, etc.):

  • Automatic schema management from ObjectType metadata
  • Optimized for transactional workloads
  • Strong consistency guarantees
  • Full transaction support

SolrDatabase (platform-solr)​

Integrates with Apache Solr for full-text search:

  • Optimized for search and faceting
  • Near real-time indexing
  • Relevance scoring and boosting
  • Eventually consistent

CachingDatabase​

Wraps another database with caching layers:

  • Query result caching
  • Reference resolution caching
  • Configurable TTLs and eviction
  • Cache invalidation on writes

AggregateDatabase​

Combines multiple databases for redundancy:

  • Writes go to all databases
  • Reads from first available
  • Automatic failover
  • Consistency checking

Data Flow​

Read Operation Flow​

Write Operation Flow​

Multi-Database Configuration​

Applications can work with multiple databases simultaneously:

Access databases by name:

1
Database defaultDb = Database.Static.getDefault();
2
Database searchDb = Database.Static.getInstance("search");
3
4
// Or use Query.using()
5
Query.from(Article.class)
6
.using(searchDb)
7
.where("_any matches ?", "search term")
8
.selectAll();

Key Design Decisions​

No Joins, Use Subqueries​

DB plugin deliberately avoids joins in favor of subqueries and reference resolution:

1
// Instead of a join
2
Query.from(Article.class)
3
.where("author = ?", Query.from(Author.class)
4
.where("name = ?", "John Doe"))
5
.selectAll();

This approach:

  • Works consistently across all database types (SQL, NoSQL, search engines)
  • Enables better caching strategies
  • Simplifies query optimization
  • Allows transparent cross-database queries

String-Based Predicates​

Queries use string-based predicates instead of method chains:

1
// DB plugin style
2
.where("publishDate > ? and author/name = ?", date, name)
3
4
// vs. alternative method-chain style
5
.where(field("publishDate").gt(date).and(field("author", "name").eq(name)))

Benefits:

  • More concise and readable
  • Easier to generate dynamically
  • Familiar to developers from SQL, LINQ, Cocoa
  • Supports complex nested expressions naturally

Field-Level Indexing Required​

Fields must be explicitly marked as indexed to be queried:

1
@Recordable.Indexed
2
private String title;

This design:

  • Makes performance characteristics explicit
  • Prevents accidental full table scans
  • Enables schema optimization
  • Works well with non-relational databases

Validation Before Write​

All validation occurs in memory before any database writes:

1
beginWrites();
2
save(object1); // Buffered, not written
3
save(object2); // Buffered, not written
4
commitWrites(); // Validates all, then writes all or none

Benefits:

  • Fast failure without database roundtrips
  • Atomic batch operations
  • Consistent error handling
  • Better error messages (all errors at once)