The Annette Data Dictionary DSL allows you to declaratively describe all elements of your data model.
- 🏛️ Domain: The root element describing the subject area.
- 📦 Components: Hierarchical grouping for decomposing large models.
- 🏷️ Labels: Flexible mechanism for customization (e.g., overriding a field's data type, specifying Java/Go package).
- 📝 Enumerations: Types with a fixed set of values.
- 🧩 Data Elements: Named types with business semantics (e.g., EmailAddress, ProductSKU) that can be reused in different tables.
- 🧩 Insertion Templates: Templates for reusing groups of fields in different tables and structures.
- 🗂️ Tables: Description of tables, their fields, primary keys, indexes, and relationships with other tables.
- 📊 Structures: Structures for DTOs and data operations. Can inherit fields from tables with the ability to exclude or include fields.
To describe a data model, you need to add the following imports:
import biz.lobachev.annette.data_dictionary.dsl.DSL.*
import biz.lobachev.annette.data_dictionary.labels.Audit.* // optional
import biz.lobachev.annette.data_dictionary.labels.ClickHouse.* // optional
import biz.lobachev.annette.data_dictionary.labels.Go.* // optional
import biz.lobachev.annette.data_dictionary.labels.Java.* // optional
import biz.lobachev.annette.data_dictionary.labels.TablePrefixSuffix.* // optional
import biz.lobachev.annette.data_dictionary.labels.Postgres.* // optionalIf you are using a domain model created in previous versions of Annette Data Dictionary (< 0.5.0), then add the
CompatDSL import, which provides partial compatibility:
import biz.lobachev.annette.data_dictionary.dsl.CompatDSL.*Domain is the root container that describes your entire subject area. It contains all other model elements (tables, enumerations, components, etc.).
📝 Declaration Syntax
The domain is declared as a variable in Scala. This variable is then used to build the final model.
val myDomain = domain("id", "name", "description")
.withLabels(/* domain labels */)
.withEnums(/* domain enumerations */)
.withDataElements(/* domain data elements */)
.withComponents(/* nested components */)
.withTables(/* domain tables */)
.withStructs(/* data models */)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique domain identifier | ✅ |
name |
Brief domain description | ✅ |
description |
Detailed description (optional, can be multi-line) | ❌ |
🧩 Configuration Methods
| Method | Purpose | Optional |
|---|---|---|
.withLabels |
Domain labels | ✅ |
.withEnums |
Enumerations | ✅ |
.withDataElements |
Data elements | ✅ |
.withComponents |
Domain components | ✅ |
.withTables |
Domain tables | ✅ |
.withStructs |
Data structures | ✅ |
.withLabeledStructs |
Labeled structures | ✅ |
💡 Recommendations
-
If the model is small, all elements can be placed in a single file.
-
For large models (tens or hundreds of tables), it is recommended to decompose the domain into components:
.withComponents( financeComponent, logisticsComponent )
This facilitates maintenance and allows distributing responsibility among teams.
🔗 See also: Components
Components allow decomposing the domain model into separate parts. They can contain the same elements as a domain: nested components, labels, enumerations, tables, and structures.
📘 Example
val comp = component("id", "name", "description")
.withLabels(/* labels */)
.withEnums(/* enumerations */)
.withDataElements(/* data elements */)
.withComponents(/* nested components */)
.withTables(/* tables */)
.withStructs(/* structures */)
.withLabeledStructs(/* structure labels */)(/* structures */)⚙️ Features
- Components can include other components, forming a hierarchy.
- Elements defined in one component (e.g.,
enumordataElement) can be used in other components of the same domain. - Components do not impose restrictions on cross-use of elements.
Labels (labels) are a flexible mechanism for customizing artifact generation.
A label is a key-value pair:
postgresSchema("core")📗 Label Types
| Type | Where Applied | Example |
|---|---|---|
| Direct | On a specific element (field, table) | postgresDataType("uuid") |
| Hierarchical | At the element (table or structure), component, or domain level | postgresSchema("finance") |
🧭 Hierarchical Label Lookup Mechanism
When generating an artifact, the label is searched from the bottom up:
- At the element level itself (e.g., table)
- At the component level where the element is defined
- At the parent component level
- At the domain level
If the label is not found, the default value is used
(e.g., PostgreSQL schema
public).
💡 Example: The
postgresSchemalabel defines the PostgreSQL schema where the table will be created. If not explicitly set, the table will be placed in thepublicschema.
Audit labels control the generation of database audit scripts. Labels are hierarchical.
| Label | Description | Example |
|---|---|---|
Audit.audit |
Defines the audit table name | Audit.audit("audit") |
Audit.disableAudit |
Excludes the table and/or component from audit | Audit.disableAudit() |
ClickHouse labels control the generation of ClickHouse DDL scripts.
| Label | Description | Example |
|---|---|---|
ClickHouse.clickHouseEngine |
Defines the ClickHouse engine to be used when creating the table. Hierarchical label. Default MergeTree(). |
ClickHouse.clickHouseEngine("ReplacingMergeTree()") |
ClickHouse.clickHouseExclude |
Excludes the table and/or component from ClickHouse DDL script generation. Hierarchical label. | ClickHouse.clickHouseExclude() |
ClickHouse.clickHouseDataType |
Defines the field type for a table and/or data element. Direct label. | ClickHouse.clickHouseDataType("IPv4") |
PostgreSQL labels control the generation of PostgreSQL DDL scripts.
| Label | Description | Example |
|---|---|---|
Postgres.postgresSchema |
Defines the PostgreSQL schema to be used when creating the table. Hierarchical label. Default public. |
Postgres.postgresSchema("finance") |
Postgres.postgresDataType |
Defines the field type for a table and/or data element. Direct label. | Postgres.postgresDataType("POINT") |
Table prefix/suffix labels control the generation of PostgreSQL and ClickHouse table names by adding a prefix or suffix to the table name. Labels are hierarchical.
| Label | Description | Example |
|---|---|---|
TablePrefixSuffix.tableNamePrefix |
Sets the table name prefix. | TablePrefixSuffix.tableNamePrefix("fi") |
TablePrefixSuffix.tableNameSuffix |
Sets the table name suffix. | TablePrefixSuffix.tableNameSuffix("users") |
Go labels control the generation of Go table entity and structure code.
| Label | Description | Example |
|---|---|---|
Go.goTablePackage |
Defines the package name for table entities. Hierarchical label. Default "model". | Go.goTablePackage("github.com/valerylobachev/finance/logic/repository/entity") |
Go.goStructPackage |
Defines the package name for structures. Hierarchical label. Default "data_model". | Go.goStructPackage("github.com/valerylobachev/finance/logic/model") |
Go.goStructSourceFile |
Defines the file for structures. Hierarchical label. | Go.goStructSourceFile("debtor") |
Go.goEnumPackage |
Defines the package name for enumerations. Hierarchical label. Default "enums". | Go.goEnumPackage("github.com/valerylobachev/finance/logic/model") |
Go.goDataType |
Defines the field type for a table and/or data element. Direct label. | Go.goDataType("complex64") |
Java labels control the generation of Java/Scala/Kotlin table entity and structure code.
| Label | Description | Example |
|---|---|---|
Java.javaTablePackage |
Defines the package name for table entities. Hierarchical label. Default "model". | Java.javaTablePackage("finance.data.fi.bp.entity"), |
Java.javaStructPackage |
Defines the package name for structures. Hierarchical label. Default "data_model". | Java.javaStructPackage("finance.data.fi.bp.model"), |
Java.javaEnumPackage |
Defines the package name for enumerations. Hierarchical label. Default "enums". | Java.javaEnumPackage("finance.data.fi.bp.model") |
Java.kotlinDataType |
Defines the field type for a table and/or data element. Direct label. | Java.kotlinDataType("BigInteger") |
Typescript labels control the generation of Typescript structure code.
| Label | Description | Example |
|---|---|---|
Typescript.tsStructPackage |
Defines the path to structure files. Hierarchical label. | Typescript.tsStructPackage("finance/data/fi/bp/model"), |
Typescript.tsEnumPackage |
Defines the path to enumeration files. Hierarchical label. | Typescript.tsEnumPackage("finance/data/fi/bp/model") |
🔤 String Types:
| Data Type | PostgreSQL | ClickHouse | Golang | Kotlin |
|---|---|---|---|---|
| varchar(length) | varchar | FixedString | string | String |
| char(length) | char | FixedString | string | String |
| string | text | String | string | String |
🔢 Numeric Types
| Data Type | PostgreSQL | ClickHouse | Golang | Kotlin |
|---|---|---|---|---|
| goInt | bigint | Int64 | int | Long |
| smailint | smallint | Int16 | int16 | Short |
| integer | integer | Int32 | int32 | Int |
| long | bigint | Int64 | int64 | Long |
| smallSerial | smallserial | Int16 | int16 | Short |
| serial | serial | Int32 | int32 | Int |
| bigSerial | bigserial | Int64 | int64 | Long |
| uint | ❌ | UInt64 | uint | ❌ |
| int8 | ❌ | Int8 | int8 | Byte |
| uint8 | ❌ | UInt8 | uint8 | ❌ |
| uint16 | ❌ | UInt16 | uint16 | ❌ |
| uint32 | ❌ | UInt32 | uint32 | ❌ |
| uint64 | ❌ | UInt64 | uint64 | ❌ |
| decimal(scale, precision) | decimal | Decimal | decimal.Decimal | java.math.BigDecimal |
| float | real | Float32 | float32 | Float |
| double | double precision | Float64 | float64 | Double |
🔲 Boolean Data Type:
| Data Type | PostgreSQL | ClickHouse | Golang | Kotlin |
|---|---|---|---|---|
| bool | boolean | Bool | bool | Boolean |
⏰ Date and Time
| Data Type | PostgreSQL | ClickHouse | Golang | Kotlin | Notes |
|---|---|---|---|---|---|
| instant | timestamptz | DateTime64(9) | time.Time | java.time.Instant | Timestamp with time zone |
| offsetDateTime | timestamptz | DateTime64(9) | time.Time | java.time.OffsetDateTime | Timestamp with time zone |
| dateTime | timestamp | DateTime | civil.DateTime | java.time.LocalDateTime | Timestamp without time zone |
| date | date | Date | civil.Date | java.time.LocalDate | Date |
| time | time | Time | civil.Time | java.time.LocalTime | Time |
📋 JSON and UUID Data Types:
| Data Type | PostgreSQL | ClickHouse | Golang | Kotlin | Notes |
|---|---|---|---|---|---|
| json | json | JSON | string | String | JSON in text representation |
| jsonb | jsonb | JSON | string | String | JSON in binary representation |
| uuid | uuid | uuid | string | UUID | UUID |
🗂 Enumeration Data Types:
To use enumerations, the syntax enumeration(enumId) is used, where enumId is the enumeration identifier.
The data type depends on the type of enumeration used:
| Enum Type | PostgreSQL | ClickHouse | Golang | Kotlin |
|---|---|---|---|---|
| NativeEnum | varchar | FixedString | string | String |
| StringEnum | varchar | FixedString | string | String |
| IntEnum | integer | Int32 | int32 | Int |
🧩 Data Elements:
To use a data type from a data element, the syntax dataElement(dataElementId) is used, where dataElementId is the
data element identifier.
The data type is determined by the data type in the data element.
🧱 Data Types for Structures:
The data types listed below are only allowed in structures:
| Data Type | Golang | Kotlin | Notes |
|---|---|---|---|
| array(datatype) | []datatype | List | Array of the specified data type |
| set(datatype) | golang_set.Set[datatype] | Set | Set of the specified data type |
| stringMap(datatype) | [string]datatype | Map<String, datatype> | Map of the specified data type |
| struct(structId) | object | object | Object of structure structId |
| linkedStruct(structId, [relation]) | object | object | Object of structure structId (only for structures associated with tables and having relations) |
| structArray(structId) | []object | List | Array of objects of structure structId |
| linkedStructArray(structId, [relation]) | []object | List | Array of objects of structure structId (only for structures associated with tables and having relations) |
Enumerations allow implementing data types with a fixed set of valid values. The following types of enumerations are possible:
- Native - implemented at the PostgreSQL level
- String - implemented as a string field of a specified length
varchar(length) - Integer - implemented as an integer field
A native enumeration is defined as:
nativeEnum("id", "name", length, "description")
.withValues(/* enumeration values */)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique enumeration identifier | ✅ |
name |
Brief enumeration description | ✅ |
length |
Maximum number of characters in the enumeration | ✅ |
description |
Detailed enumeration description. | ❌ |
Enumeration values are specified as: <value> -> <constant name> -> <value description>
Example of a native enumeration:
nativeEnum("LedgerType", "Ledger Type", 1)
.withValues(
"L" -> "LeadingLedger" -> "Leading ledger",
"X" -> "ExtensionLedger" -> "Extension ledger",
)A string enumeration is implemented as a string field of a specified length varchar(length). A string enumeration can
be of the following type:
- Strict (
strictStringEnum) - when assigning a value to a field, a validity check is performed - Non-strict (
stringEnum) - any value can be assigned to the field
Example:
stringEnum("LedgerType", "Ledger Type", 1)
.withValues(
"L" -> "LeadingLedger" -> "Leading ledger",
"X" -> "ExtensionLedger" -> "Extension ledger",
)An integer enumeration is implemented as an integer field. Like a string enumeration, it can be strict strictIntEnum
or non-strict intEnum. The enumeration value must be an integer value.
The enumeration is defined as:
intEnum("id", "name", "description")
.withValues(/* enumeration values */)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique enumeration identifier | ✅ |
name |
Brief enumeration description | ✅ |
description |
Detailed enumeration description. | ❌ |
Example:
intEnum("LedgerType", "Ledger Type")
.withValues(
"0" -> "LeadingLedger" -> "Leading ledger",
"1" -> "ExtensionLedger" -> "Extension ledger",
)Data Elements are named types with business semantics (e.g., CompanyCodeId, ProductSKU) that can be reused in tables and structures.
A data element can be defined in two formats:
// new format
"id" :! "field name" :> "db field name" :# datatype :@ "name" :@@ "description" :* ( /* data element labels */ )
// old format
dataElement("id", "field name", datatype, "name")
.withLabels(/* data element labels */)
.withDbFieldName("db field name")
.withDescription("description")
⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique data element identifier | ✅ |
field name |
Field name | ✅ |
db field name |
Database field name | ❌ |
datatype |
Data type | ✅ |
name |
Name (brief description) | ✅ |
description |
Detailed description | ❌ |
Example:
// new data element format
"UserId" :! "userId" :# varchar(20) :@ "User Id" :@@ "description"
// old data element format
dataElement("CreditorId", "creditorId", varchar(10), "Creditor id")
.withLabels(/* data element labels */)
.withDescription("description")Insertion Templates (Includes) are designed for reusing groups of fields in tables and structures. If the same set of
fields is used in different tables, it can be extracted into an insertion template and used as include("inclideId") (
in table fields) or includeStructFields("inclideId") (in structure fields).
An insertion template definition has the following format:
includeDef("id", "name")
.withDescription("description")
.withFields(/* field descriptions */)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique insertion template identifier | ✅ |
name |
Name (brief description) | ✅ |
description |
Detailed description | ❌ |
field descriptions |
List of template fields. The field description format is described in the Fields section | ✅ |
Example template for record modification fields:
includeDef("Modification", "Modification data structure")
.withFields(
"updatedBy" :# "UserId" :@ "User updated record",
"updatedAt" :# instant :@ "Timestamp of record update" :== "CURRENT_TIMESTAMP"
)Tables are a key element of the data model. They are described in the following format:
table("id", "name", "entity name")
.withTableName("db table name")
.withDescription("description")
.withLabels(/* table labels */)
.withPK(/* primary key field definitions */)
.withFields(/* field definitions */)
.withIndexes(/* index definitions */)
.withRelations(/* relationship definitions */)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique table identifier. Converted to PascalCase. | ✅ |
name |
Name (brief description) of the table. | ✅ |
entity name |
Class (structure) name of the entity. If not specified, taken from id. |
❌ |
db table name |
Table name. If not specified, the entity name (entity name) converted to plural and snake_case is used. |
❌ |
description |
Detailed description (optional, can be multi-line). | ❌ |
🧩 Configuration Methods
| Method | Purpose | Optional |
|---|---|---|
.withLabels |
Table labels | ✅ |
.withPK |
Describes primary key fields | ✅ |
.withFields |
Describes table fields | ✅ |
.withRelations |
Describes relationships (one-to-one, many-to-one) | ✅ |
.withIndexes |
Describes indexes | ✅ |
Example table definition:
.withTables(
table("Country", "Country", "Country")
.withPK(
"id" :# "CountryId",
)
.withFields(
"name" :# "Name",
"key" :#? varchar(3) :@ "Country Key",
dataElementTableField("LanguageId"),
include("Modification")
)
.withIndexes(
uniqueIndex("countryKey", "Country key must be unique", "key"),
)
.withRelations(
manyToOne("languageId", "Reference to language", "Language", "languageId" -> "id")
.withAssociation("language"),
)Table fields are specified in the primary key sections (method .withPK) and in the field sections (method
.withFields) of the table definition. The following parameters are set for a table field:
- Field name in the class (structure)
- Field data type
- Database table field name (default is the field name converted to snake_case)
- Name (brief description) of the field (default is empty)
- Detailed field description (default is empty)
NULL/NOT NULLconstraint (default isNOT NULLfield)- Default value (default is not set)
- Generated column parameters (default is not set)
- Field labels (default is not set)
The following syntax is used to create a field:
| Syntax | Description |
|---|---|
"fieldName" :# datatype |
Creates a field with name fieldName and type datatype |
"fieldName" :#? datatype |
Creates a field with name fieldName, type datatype and NULL constraint |
"fieldName" :#++ datatype |
Creates a field with name fieldName, type datatype and GENERATED ALWAYS AS IDENTITY generation |
dataElementTableField("dataElementId") |
Creates a field with parameters (name, type, description, etc.) from the specified data element dataElementId |
include("includeId") |
Creates a set of fields from the specified insertion template includeId |
The data type datatype can be specified
- as a value indicated in the Data Types section,
- as a string
"dataElementId"in which case the field parameters are taken from the specified data elementdataElementId(if they are not explicitly defined in the field description)
The following syntax is used to change field parameters
| Syntax | Description |
|---|---|
:> "dbFieldName" |
Sets the database table field name dbFieldName |
:@ "name" |
Sets the name (brief description) of the field name |
:@@ "description" |
Sets the detailed field description description |
:*(/* labels */) |
Sets field labels |
:= default-value |
Sets the default value. The default value can be a string, number, or boolean value |
:== "statement" |
Sets the default value as an expression statement. For example: :== "CURRENT_TIMESTAMP" - current time |
:& modificatior |
Sets field parameters based on the modifier value modificatior. Valid modifier values are listed below. |
Valid modifier values:
| Modifier | Description |
|---|---|
Null |
Sets the NULL constraint |
AutoInc |
Sets the generated column parameters GENERATED ALWAYS AS IDENTITY |
GenAlwaysAsIdentity |
Sets the generated column parameters GENERATED ALWAYS AS IDENTITY |
GenByDefaultAsIdentity |
Sets the generated column parameters GENERATED BY DEFAULT AS IDENTITY |
GenStoredAlwaysAs("statement") |
Sets the generated column parameters GENERATED ALWAYS AS( $statement ) STORED |
GenVirtualAlwaysAs("statement") |
Sets the generated column parameters GENERATED ALWAYS AS( $statement ) VIRTUAL |
Table relationships are specified in the .withRelations method. The following parameters are set for a relationship:
- Relationship identifier
- Name (brief description) of the relationship
- Detailed relationship description
- Relationship labels
- Relationship type:
OneToOne,ManyToOne - Identifier of the related table
- Related fields as a list of
"fieldOfTableA" -> "fieldOfTableB" onUpdate/onDeleteactions:Cascade,Restrict,NoAction,SetNull,SetDefault. Default isNoAction.- Logical relationship flag - a logical relationship does not create a
FOREIGN KEYconstraint in the database - Names of associated fields in entity structures
Relationships are described in the following form:
// real OneToOne relationship
oneToOne("id", "name", "relatedTableId", relatedFieldList)
// logical OneToOne relationship
logicalOneToOne("id", "name", "relatedTableId", relatedFieldList)
// real ManyToOne relationship
manyToOne("id", "name", "relatedTableId", relatedFieldList)
// logical ManyToOne relationship
logicalManyToOne("id", "name", "relatedTableId", relatedFieldList)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique relationship identifier | ✅ |
name |
Name (brief description) of the relationship | ✅ |
relatedTableId |
Identifier of the related table | ✅ |
relatedFieldList |
Related fields as a list of "fieldOfTableA" -> "fieldOfTableB", separated by commas |
✅ |
🧩 Configuration Methods
| Method | Description | Optional |
|---|---|---|
.withDescription("description") |
Detailed relationship description | ✅ |
.withLabels(/* labels */) |
Relationship labels | ✅ |
.withOnUpdate(foreignKeyAction) |
onUpdate actions. Valid foreignKeyAction values: Cascade, Restrict, NoAction, SetNull, SetDefault |
✅ |
.withOnDelete(foreignKeyAction) |
onDelete actions. Valid foreignKeyAction values: Cascade, Restrict, NoAction, SetNull, SetDefault |
✅ |
.withAssociation(association) |
Names of associated fields in entity structures | ✅ |
The withAssociation method allows defining associative fields in entity structures.
For example, for a relationship defined in the Country table:
manyToOne("languageId", "Reference to language", "Language", "languageId" -> "id")
.withAssociation("language")the one-way association language means that a field Language *LanguageEntity referencing the LanguageEntity entity
will be added to the CountryEntity entity.
An association can be two-way. For example, for a relationship defined in the BusinessPartner table:
oneToOne("debtorId", "Reference to Debtor", "Debtor", "debtorId" -> "id")
.withAssociation("debitor" -> "businessPartner")the two-way association "debitor" -> "businessPartner" means that a field Debitor *DebtorEntity referencing the
DebtorEntity entity will be added to the BusinessPartnerEntity entity, and a field
BusinessPartner *BusinessPartner referencing the BusinessPartnerEntity entity will be added to the DebtorEntity
entity.
Table indexes are specified in the .withIndexes method. The following parameters are set for indexes:
- Index identifier
- Name (brief description) of the index
- Detailed index description
- Relationship labels
- Unique index
UNIQUEflag - Index fields
- Indexing method:
BTreeMethod,HashMethod,GistMethod,SpGistMethod,GinMethod,BrinMethod - Indexing condition
WHERE - List of fields to be included in the index as non-key columns
An index is described in the following form:
// non-unique index
index("id", "name", indexFields)
.withDescription(description)
.withLabels(/* index labels */)
.withMethod(indexMethod)
.where(predicate)
.include(includeFields)
// unique index
uniqueIndex("id", "name", indexFields)
.withDescription(description)
.withLabels(/* index labels */)
.withMethod(indexMethod)
.where(predicate)
.include(includeFields)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique index identifier | ✅ |
name |
Name (brief description) of the index | ✅ |
indexFields |
List of index fields, separated by commas. Detailed description of the field list format see below. | ✅ |
🧩 Configuration Methods
| Method | Description | Optional |
|---|---|---|
.withDescription("description") |
Detailed index description | ✅ |
.withLabels(/* labels */) |
Index labels | ✅ |
.withMethod(indexMethod) |
Indexing method. Valid values: BTreeMethod, HashMethod, GistMethod, SpGistMethod, GinMethod, BrinMethod |
✅ |
.where(predicate) |
Indexing condition WHERE. The predicate contains a logical expression. For example: cor_tip IN ('I', 'U') |
✅ |
.include(includeFields) |
Adds a list of fields to the index that will be included as non-key columns | ✅ |
The list of index fields can be defined using the following syntax:
| Syntax | Description |
|---|---|
"fieldName" |
Index on field fieldName, sorted in ascending order. |
asc("fieldName") |
Index on field fieldName, sorted in ascending order. |
desc("fieldName") |
Index on field fieldName, sorted in descending order. |
asc("fieldName").nullsFirst |
Index on field fieldName, sorted in ascending order and NULL values are placed first after sorting. |
desc("fieldName").nullsLast |
Index on field fieldName, sorted in descending order and NULL values are placed last after sorting. |
Example:
index("filter", "Index to filter order by orderDate, customer & status", desc("orderDate"), "customerId", "status")
.where("status <> 'DRAFT")
.include("id", "postingDate")
,Structures are used to describe DTO objects and data operations. They are described in the following format:
struct("id", "name", "struct name")
.withDescription("description")
.withLabels(/* table labels */)
.likeTable("tableId", fieldUsage)
.likeTablePK("tableId")
.withFields(/* field definitions */)⚙️ Arguments and Parameters
| Parameter | Description | Mandatory |
|---|---|---|
id |
Unique structure identifier. Converted to PascalCase. | ✅ |
name |
Name (brief description) of the structure | ✅ |
struct name |
Structure name. If not specified, taken from id. |
❌ |
description |
Detailed description (optional, can be multi-line) | ❌ |
🧩 Configuration Methods
| Method | Description | Optional |
|---|---|---|
.withLabels |
Structure labels | ✅ |
.likeTable("tableId", fieldUsage) |
Forms a structure including the fields of table tableId. The inclusion rules are defined in fieldUsage (see below) |
✅ |
.likeTablePK("tableId") |
Forms a structure including the primary key fields of table tableId |
✅ |
.withFields |
Describes structure fields | ✅ |
The likeTable method allows creating a structure similar to the table tableId specified in the method. To determine
which fields from the table will be included in the structure, the fieldUsage rule is used:
| Rule | Description |
|---|---|
IncludeAllFields |
All table fields are included in the structure |
IncludePKFields |
All primary key fields of the table are included in the structure |
IncludeFields(fieldList) |
The table fields listed in fieldList are included in the structure |
ExcludeFields(fieldList) |
All table fields except those listed in fieldList are included in the structure |
Structure fields are specified in the .withFields method of the structure definition. The following parameters are set
for a structure field:
- Field name in the class (structure)
- Field data type
- Name (brief description) of the field (default is empty)
- Detailed field description (default is empty)
- Field optionality (default is non-optional field)
The following syntax is used to create a field:
| Syntax | Description |
|---|---|
"fieldName" :# datatype |
Creates a mandatory field with name fieldName and type datatype |
"fieldName" :##? datatype |
Creates an optional field with name fieldName and type datatype |
dataElementStructField("dataElementId") |
Creates a field with parameters (name, type, description, etc.) from the specified data element |
includeStructFields("includeId") |
Creates a set of fields from the insertion template includeId |
The following syntax is used to change field parameters
| Syntax | Description |
|---|---|
:@ "name" |
Sets the name (brief description) of the field name |
:@@ "description" |
Sets the detailed field description description |
The data type datatype can be specified
- as a value indicated in the Data Types section,
- as a string
"dataElementId"in which case the field parameters are taken from the specified data elementdataElementId(if they are not explicitly defined in the field description)
For structures that are associated with tables (using the likeTable method), the data types linkedStruct and
linkedStructArray can be used.
The data type linkedStruct(structId, [relation]) adds a new field to the structure referencing the structure
structId.
This structure must also be associated with a table. There must be a relationship (real or logical) between the two
associated tables that links these tables. If there is only one relationship between the tables, it can be omitted. If
there are several, specifying the relationship relation is mandatory. Two ways of specifying the relationship are
allowed:
OwnRelation(relationId)- the relationshiprelationIdis defined in the table associated with the own structure ( i.e., the structure in which the field is defined)OwnRelation(relationId)- the relationshiprelationIdis defined in the table associated with the structurestructId)
The data type linkedStructArray(structId, [relationId]) adds a new field to the structure that is an array referencing
the structure structId.
This structure must also be associated with a table. There must be a relationship (real or logical) between the two
associated tables that links these tables. If there is only one relationship between the tables, it can be omitted. If
there are several, specifying the relationship relationId, which is defined in the table associated with the own
structure (i.e., the structure in which the field is defined), is mandatory.
Using the data types linkedStruct and linkedStructArray allows establishing a connection between two structures that
are associated with two related tables. This information makes it possible to generate source code that will load data
from two tables into the specified structures.
To generate artifacts, preliminary model building is required, during which the consistency of the domain model is checked: uniqueness of names, existence of relationships, correctness of types, and much more.
Building is performed with the following expression:
val build = DomainBuilder.build(OrderDomain.data)where OrderDomain.data is the domain data model described in Annette Data Dictionary DSL.
Depending on the returned result, either errors are output (if the model contains them) or artifacts are generated:
build match {
case Left(messages) =>
// output errors
messages.foreach(println)
case Right(result) =>
// output warnings
result.warnings.foreach(println)
// the built data model can be used to generate artifacts
val domain = result.domain
// ...
}To generate PostgreSQL DDL scripts, use PostgresDDLGenerator:
import biz.lobachev.annette.data_dictionary.generators.Generator
import biz.lobachev.annette.data_dictionary.generators.postgres_ddl.PostgresDDLGenerator
Generator.generate(
PostgresDDLGenerator(
domain = domain, // Built model
filename = "pg_ddl.sql", // Name of the generated script file. Optional. Default is postgres_ddl.sql
),
s"examples/${domain.id}/ddl", // Path where the generated file will be located
)To generate ClickHouse DDL scripts, use ClickHouseDDLGenerator:
import biz.lobachev.annette.data_dictionary.generators.Generator
import biz.lobachev.annette.data_dictionary.generators.click_house_ddl.ClickHouseDDLGenerator
Generator.generate(
ClickHouseDDLGenerator(
domain = domain, // Built model
filename = "pg_ddl.sql", // Name of the generated script file. Optional. Default is click_house_ddl.sql
),
s"examples/${domain.id}/ddl", // Path where the generated file will be located
)To generate the domain data model in Excel format, use ClickHouseDDLGenerator:
import biz.lobachev.annette.data_dictionary.generators.Generator
import biz.lobachev.annette.data_dictionary.generators.xls_domain.DomainExcelGenerator
Generator.generate(
DomainExcelGenerator(
target = GoTarget, // Target language whose data types will be reflected in the generated file. Valid values: GoTarget, KotlinTarget
filename = "go-finance-domain", // File name, without the `.xlsx` extension
translation = Map.empty, // Column header translations
domains = domain1, domain2, // List of domain models to be described in the file
),
s"examples/${domain.id}", // Path where the generated file will be located
)To generate the domain model schema in DBML format, use DbmlGenerator:
import biz.lobachev.annette.data_dictionary.generators.Generator
import biz.lobachev.annette.data_dictionary.generators.dbml.DbmlGenerator
Generator.generate(
DbmlGenerator(
domain = domain, // Built model
filename = "my-domain.dbml", // Name of the generated script file. Optional. Default is diagram.dbml
),
s"examples/${domain.id}", // Path where the generated file will be located
)The generated schema can be used:
- in the editor at dbdiagram.io
- in the drawDB editor
To generate Excel data templates for database loading, use ExcelDataTemplateGenerator:
import biz.lobachev.annette.data_dictionary.generators.Generator
import biz.lobachev.annette.data_dictionary.generators.xls_data.ExcelDataTemplateGenerator
Generator.generate(
Generator.generate(
ExcelDataTemplateGenerator(
domain, // Built model
),
s"examples/${domain.id}/xls_data", // Path where the generated files will be located
)To generate Kotlin source code, use KotlinGenerator:
import biz.lobachev.annette.data_dictionary.generators.Generator
import biz.lobachev.annette.data_dictionary.generators.kotlin.KotlinGenerator
Generator.generate(
Generator.generate(
KotlinGenerator(
domain, // Built model
),
s"examples/${domain.id}/kotlin", // Path where the generated files will be located
)To generate Go source code, use GoGenerator.
import biz.lobachev.annette.data_dictionary.generators.Generator
import biz.lobachev.annette.data_dictionary.generators.golang.{GoGenerator, GoGeneratorOptions, Gorm, Sqlx}
Generator.generate(
GoGenerator(
domain, // Built model
GoGeneratorOptions(options), // Generation options
),
s"examples/${domain.id}/go", // Path where the generated files will be located
)Generation options can take the following values: