Overview
Cube's bet: BI is an API, not a product
Where the dbt Semantic Layer puts metrics at the top of the abstraction, Cube puts the cube itself — a joinable, multidimensional model — and exposes it as a real API surface (REST, GraphQL, Postgres-wire SQL). The result is a headless BI layer: any consumer that speaks any of those protocols sees the same measures and dimensions.
Anatomy of a cube
cube('Orders', {
sql: `SELECT * FROM analytics.fct_orders`,
joins: {
Users: { sql: `${CUBE}.user_id = ${Users}.id`, relationship: 'belongsTo' },
Products: { sql: `${CUBE}.product_id = ${Products}.id`, relationship: 'belongsTo' },
},
measures: {
revenue: { sql: `amount_cents / 100.0`, type: 'sum' },
orderCount: { sql: `id`, type: 'count' },
aov: { sql: `${revenue} / ${orderCount}`, type: 'number' },
},
dimensions: {
country: { sql: `country`, type: 'string' },
productLine:{ sql: `product_line`, type: 'string' },
orderTs: { sql: `order_ts`, type: 'time' },
},
});
Three things to notice:
- Joins are first-class graph edges, not buried inside a
SELECT. Cube treats the cube graph as a property graph and walks it on demand. - Measures can compose (
aovreferencesrevenueandorderCount) — the metric tree, but expressed inline. - Time is a dimension with
type: 'time', not a magic column. Granularity (day,week,month,quarter,year) is requested at query time.