Introducing XDB

XDB is a new kind of database library based on tuples.

Rather than writing database specific schemas, queries, and migrations, XDB allows developers to model their domain once and use it with one or more databases.

XDB separates the application domain model from the underlying database(s) by using a simple, yet powerful, data model based on tuples. It allows application developers to focus on modeling, ingesting, and querying data without worrying about the underlying database infrastructure and operations.

Why?

Not all databases are created equal. Each database comes with its own features and tradeoffs. Most applications, at scale, have multiple types of databases:

  • PostgreSQL/MySQL as the main database
  • Redis for caching
  • Elasticsearch for search
  • Clickhouse for analytics
  • BigTable for versioning

Every database solves a specific problem and has different tradeoffs.

An application domain model is often a combination data that resides in different databases. Typically, each database has its own "layer" abstracting away the migrations and queries. Alternatively, new microservices are created spunup to manage specific usecases like search, analytics, caching, etc.

At end of the day, developers have to stitch together domain data from multiple databases or microservices to serve user-facing APIs. Looking at an end-to-end flow, there are several layers data fetching, mutations, transformations, etc. Every time a feature adds new fields, relationships, etc. the entire stack goes through a churn.

XDB is an attempt to separate application domain model from database implementation and operations. What if, instead of maintaining multiple database-specific implementations, developerscan model their domain once and seamlessly work with multiple databases.

Inspiration

XDB draws inspiration from two key concepts - data services and N-Quads.

Data services are intermediary services that sit between the API and databases. Data services provide simple APIs for your domain data, while automating and abstracting away the underlying database management.

Data Services
Data Services

N-Quad is a well-known format often used to represent attributes and relationships in graphs. Dgraph extended extended the N-Quad format to support additional metadata for relationships.

<Post:9bsv0s5ocl6002kdg0fg> <title> "Hello World" .
<Post:9bsv0s5ocl6002kdg0fg> <description> "..." .
<Post:9bsv0s5ocl6002kdg0fg> <author> <User:1> .
<Post:9bsv0s5ocl6002kdg0fg> <created_at> "2025-04-01T00:00:00Z"
<Post:9bsv0s5ocl6002kdg0fg> <tags> <Tag:golang> .
<Post:9bsv0s5ocl6002kdg0fg> <tags> <Tag:xdb> .

XDB was inspired by Dgraph's Mutation API that uses N-Quads to insert or update data. What if this idea was extended to build an abstraction that could be used with any database?

Data Model

XDB is built around three core concepts - Tuple, Edge, and Record.

Tuple is a combination of a key, name, value, and optional metadata.

Tuple
Tuple

This simple yet powerful structure can represent any domain model while being easily mappable to various database formats.

Edge is a special kind of tuple whose value is also a key. Edges are unidirectional and are used to represent relationships between records.

Record is a collection of tuples and edges that share the same key. Records are similar to objects, structs or rows in a database.

Developer Experience

XDB is designed for building and managing data services. It can also be used to replace ORMs and traditional repository/database layers in applications.

Here's how you can use XDB:

// Start by creating tuples and edges.
key := xdb.NewKey("Post", "9bsv0s5ocl6002kdg0fg")

tuples := []*xdb.Tuple{
	xdb.NewTuple(key, "title", "Hello World"),
	xdb.NewTuple(key, "description", "..."),
	xdb.NewTuple(key, "created_at", time.Now()),
}

edges := []*xdb.Edge{
	xdb.NewEdge(key, "author", xdb.NewKey("User", "1")),
	xdb.NewEdge(key, "tags", xdb.NewKey("Tag", "golang")),
	xdb.NewEdge(key, "tags", xdb.NewKey("Tag", "xdb")),
}

// Use any of the driver implementations to store
// the tuples and edges.
memstore := xdbmemory.New()

_ = memstore.PutTuples(ctx, tuples...)
_ = memstore.PutEdges(ctx, edges...)

// OR store the tuples and edges in different databases.
tuplestore := xdbpostgres.New()
edgestore := xdbredis.New()

_ = edgestore.PutEdges(ctx, edges...)
_ = tuplestore.PutTuples(ctx, tuples...)

// Fetch the tuples and edges using Get APIs.
keys := []*xdb.Key{
	xdb.NewKey("Post", "9bsv0s5ocl6002kdg0fg"),
}
attrNames := []string{"title", "description", "created_at"}
edgeNames := []string{"author", "tags"}

tuples, _ := tupleStore.GetTuples(ctx, keys, attrNames)
edges, _ := edgeStore.GetEdges(ctx, keys, edgeNames)

// Alternatively, use higher-level Record APIs
post := xdb.NewRecord(key).
	Set("title", "Hello World").
	Set("description", "...").
	Set("created_at", time.Now()).
	AddEdge("author", xdb.NewKey("User", "1")).
	AddEdge("tags", xdb.NewKey("Tag", "golang")).
	AddEdge("tags", xdb.NewKey("Tag", "xdb"))

_ = memstore.PutRecord(ctx, post)

// Fetch the full post
post, _ = memstore.GetRecord(ctx, key)