Skip to main content
ElastiCORE 3.0 is a release candidate: 3.0.261022-rc1 on Maven Central. Projects on the 2.2 line: see the 2.2 documentation.
Version: 3.0

Apache Ossie semantic model (ossie-export)

Apache Ossie (incubating) is the ASF's vendor-neutral standard for exchanging semantic metadata - datasets (tables), fields (columns), relationships (joins) and metrics - between analytics, BI and AI platforms. An ElastiCORE blueprint already says everything the dataset side asks for, so ElastiCORE exports it instead of having it declared a second time. The export follows core spec 0.2.0.dev0 (core-spec/spec.md, ossie-schema.json).

Agents and BI tools that do not know what the data means get, from the same DSL: the joins, what a code like PLC stands for, and what "revenue" is.

Turning it on​

Either in build.gradle - the bundle stays as it is, the export is added:

build.gradle
elasticore {
architecture = 'hexagonal'
rules = ['ossie-export']
}

or with the bundle in a domain's env.yml:

blueprint/<domain>/env.yml
    publisher:
rules: [hexagonal-springboot, ossie-export]

Every generation rewrites src/main/resources/blueprint/semantic_model.yml at the blueprint root: the whole project is one model, so relationships across domains are not cut. It is a sidecar like schema.yml - always regenerated, never edited by hand. A failed export degrades the export, never the build. The spec is still a dev version and may change.

What is exported​

DSLOssieNotes
an entity (not @abstract, not @embeddable)datasets[]source = the table (@table, or table.prefix + name)
-- labeldescription
@id fields (composite too)primary_key
@unique, @Index(unique="true")unique_keys
a value fieldfields[] + expression (the column, OSSIE_SQL_2026) + datatypethe type table below
@formula(sql)expression = that SQLan expression in OSSIE_SQL_2026; one with a subquery as a single ANSI_SQL entry - see Dialects
fields of @extend(Parent)the child dataset's fieldsinherited columns are in that table too
an @Embedded value objectits columns (deliveryAddress.city)@embedded(prefix) gives prefix_city
an enum fielddatatype (String for a text code or the name, Integer for an ordinal) + ai_context: "Codes: PLC = Placed, ..."the table holds only the code
a reference to another entityrelationships[] + the join column fieldthe real join column
@password, @writeonlynot exportedvalues the application never reads back; listed in omitted_columns
states:, @access, @owner, @eventcustom_extensions (ELASTICORE)Ossie has no words for them
@compute, @snapshot, money / quantitythe field's custom_extensionsderivation rules and value types
metrics:metrics[] (OSSIE_SQL_2026, datatype, description)only the declared ones - see Metrics
@terms(...) (entity, field, measure)ai_context.synonymsbusiness terms

DataTypes​

Ossie has ten DataTypes: String, Integer, Decimal, Float, Boolean, Date, Time, DateTime (no time zone), DateTimeTz (an instant), Opaque (outside the vocabulary).

DSLOssieDSLOssie
string, text, clobStringfloat, doubleFloat
int, long, biginteger, quantityIntegerbooleanBoolean
decimal, bigdecimal, moneyDecimaldate / timeDate / Time
an enumString for a text code or the name, Integer for an ordinal (codes in ai_context)datetimeDateTime
json, blob, ObjectOpaquetimestampDateTimeTz

is_time is not written: the spec derives it from the datatype, and that answer is this model's.

Dialects​

The spec registers twelve dialect names but defines the meaning of one, OSSIE_SQL_2026 (core-spec/expression_language.md: column references, arithmetic, comparison and logical operators, CASE WHEN, aggregates, COALESCE, NULLIF, CAST). The others, ANSI_SQL among them, name vendor languages a consumer may ignore. So field expressions (column names) and the expressions built from metrics: are exported as OSSIE_SQL_2026. An @formula is split by what it holds: an expression in that language (unit_price * quantity) is OSSIE_SQL_2026; one with a subquery ((select count(*) from ...)) cannot be said in it (SELECT, FROM, JOIN, subqueries and CTEs are not supported in expressions), so it is exported as one ANSI_SQL entry - a reader that understands the SQL uses it, others skip it. An @formula field also gets "formula": "database" in its custom_extensions.

What is not exported​

Measures nobody declared: inventing a revenue formula from entity fields would be exporting a guess.

An excerpt - the pet store​

blueprint/semantic_model.yml
# Apache Ossie (incubating) semantic model - core spec 0.2.0.dev0
# Generated by ElastiCORE from the blueprint DSL - do not edit;
# regenerate with the ossie-export rule set.
version: 0.2.0.dev0
name: petstore
description: "Domain model of petstore, as its ElastiCORE DSL declares it"
ai_context:
instructions: "Each dataset is a table the generated application owns. An enumeration column holds a code; the field's ai_context lists every code and its label. Amounts (money) are decimals with their scale; a quantity is a count that is never negative. Password and write-only columns are left out on purpose. Lifecycles, access rules and derived values are in the ELASTICORE custom extensions."
datasets:
- name: PurchaseOrder
source: ps_purchase_order
primary_key: [order_id]
description: "주문"
ai_context:
synonyms: ["order", "purchase", "구매"]
fields:
- name: status
expression:
dialects:
- dialect: OSSIE_SQL_2026
expression: "status"
datatype: String
ai_context: "Codes: PLC = Placed, SHP = Shipped, CAN = Cancelled"
custom_extensions:
- vendor_name: ELASTICORE
data: "{\"lifecycle\": true, \"enumeration\": \"OrderStatus\"}"
- name: city
expression:
dialects:
- dialect: OSSIE_SQL_2026
expression: "city"
datatype: String
description: "deliveryAddress.city"
- name: total_without_vat
expression:
dialects:
- dialect: OSSIE_SQL_2026
expression: "total_without_vat"
datatype: Decimal
custom_extensions:
- vendor_name: ELASTICORE
data: "{\"compute\": \"sum(lines.subTotal)\", \"value_type\": \"money\"}"
# ...
custom_extensions:
- vendor_name: ELASTICORE
data: "{\"entity\": \"PurchaseOrder\", \"domain\": \"petstore\", \"owner\": \"customerId\", \"access\": {\"*\": \"admin\", \"cancel\": \"owner\", \"create\": \"user\", \"read\": \"owner\"}, \"states\": {\"status\": {\"initial\": \"PLACED\", \"ship\": \"PLACED -> SHIPPED\", \"cancel\": \"PLACED -> CANCELLED\", \"delete\": \"CANCELLED\"}}, \"events\": \"Placed(total), Shipped, Cancelled\", \"omitted_columns\": [\"credit_card_number\"]}"
relationships:
- name: PurchaseOrder_customer
from: PurchaseOrder
to: Customer
from_columns: [customer_id]
to_columns: [customer_id]
metrics:
- name: averageOrder
expression:
dialects:
- dialect: OSSIE_SQL_2026
expression: "(SUM(CASE WHEN PurchaseOrder.status <> 'CAN' THEN PurchaseOrder.total END)) / NULLIF((COUNT(CASE WHEN PurchaseOrder.status <> 'CAN' THEN 1 END)), 0)"
description: "주문 1건 평균 금액"
datatype: Decimal
ai_context:
synonyms: ["AOV", "객단가"]

The card number of CreditCard (@writeonly) is not a field of the dataset - it is named in omitted_columns. The pet store's referenceColumn: short makes the join column customer_id.

How it is checked​

  • SemanticModelTest (the pet store and elasticore-template-ddd), on every build: every exported table, column, primary key and join column exists in the real database. A semantic model that names a column the database does not have is a query that fails in somebody's BI tool.
  • MetricsTest (the pet store): places, ships and cancels orders, then runs each exported metric's SQL on the live database (dataset names replaced by their tables) and compares it with the same numbers computed from the entities - revenue, order count, average order, shipped, cancelled, customers.
  • Both files were validated against Ossie's ossie-schema.json with no errors (2026-10-03, run by hand - not part of the build tests).
  • Unit tests in elasticore-base: OssieExportTest, OssieMetricsTest, OssieSqlTest, OssieFormulaDialectTest.

Using the file​

semantic_model.yml is meant for tools that read Ossie: a BI tool takes the datasets, joins and metrics instead of having them modelled again; an AI agent reads ai_context (codes and their labels, synonyms, the model's instructions) and the metric definitions, so "revenue last month" maps to PurchaseOrder and revenue with the business's own definition, and the time axis comes from a date field such as order_date. Lifecycles, access rules and derived values travel in the ELASTICORE custom extensions.

History​

  • Before the first public release (3.0.261015-alpha1) - the export was brought to the spec: before, it wrote a semantic_model: array without expression, money as String, no enum or embedded columns, 2.x-style join columns and the password column.
  • 3.0.261022-rc1 - expressions are exported as OSSIE_SQL_2026 (before: all ANSI_SQL, which a reader may skip entirely); only a subquery @formula stays ANSI_SQL; "formula": "database" added. count(Entity) counts the key column (COUNT(Customer.customer_id)) instead of a bare COUNT(*).