Spec
Database Modeling Extension User Manual — map proto messages to database table structures (entities).
Map proto message to database table structure (entity).
apihug/protobuf/domain/annotations.protoNEVER add any file-level option declarations in proto files:
// Forbidden to use the following options (rely on default code generation configuration)
option java_package = "...";
option java_multiple_files = true;
option go_package = "...";
All code generation configurations are automatically managed by the build system.
Domain layer (domain/) must maintain architectural purity:
// Forbidden to reference API layer messages
import "com/example/api/user/api.proto"; // Wrong!
import "com/example/api/order/request.proto"; // Wrong!
Allowed references:
// Allow referencing constant layer (infra/)
import "com/example/infra/settings/user_constant.proto";
// Allow referencing other domain entities
import "com/example/domain/entities/order.proto";
message UserEntity {
UserStatusEnum status = 1; // Enums can be shared
OrderEntity order = 2; // Domain internal reference
}
import "apihug/protobuf/domain/annotations.proto";
Note: annotations.proto imports persistence.proto, which in turn imports view.proto for entity view definitions.
From apihug/protobuf/domain/annotations.proto:
extend google.protobuf.MessageOptions {
Table table = 1772;
}
extend google.protobuf.FieldOptions {
Column column = 1773;
}
Table Level (Message):
message EntityName {
option (hope.persistence.table) = {
name: "TableName";
description: "Table description";
// Other configurations...
};
}
Column Level (Field):
string field_name = sequence [(hope.persistence.column) = {
name: "ColumnName";
description: "Column description";
nullable: false;
type: VARCHAR;
}];
syntax = "proto3";
package com.example.pet.domain;
import "apihug/protobuf/domain/annotations.proto";
message Category {
option (hope.persistence.table) = {
name: "CATEGORY",
description: "Pet category",
wires: [IDENTIFIABLE]
};
string name = 1 [(hope.persistence.column) = {
name: "NAME",
description: "Category name",
nullable: false,
length: 32,
unique: true,
type: VARCHAR
}];
}
name (string)CATEGORY, USER_ORDER)description (string)catalog (string)schema (string)unique_constraints (repeated UniqueConstraint)Fields:
name (string): Constraint namecolumn_list (repeated string): Column name arrayunique_constraints: {
name: "UK_PET_NAME_CATEGORY",
column_list: ["name", "category"]
}
indexes (repeated Index)Fields:
name (string): Index namecolumn_list (repeated string): Column name arrayunique (bool): Whether unique indexindexes: {
name: "IDX_PET_NAME",
column_list: ["name"]
}
indexes: {
name: "IDX_CATEGORY_UNIQUE",
column_list: ["category"],
unique: true
}
wires (repeated Wire enum)Function: Enable platform built-in common features
| Enum Value | Meaning | Auto-added Fields |
|---|---|---|
ALL | Enable all features | id + audit + delete flag + tenant + version |
IDENTIFIABLE | Only add primary key | id (Long) |
AUDITABLE | Audit fields | created_at, updated_at, created_by, updated_by |
DELETABLE | Logical delete | deleted (Boolean) |
TENANTABLE | Multi-tenant | tenant_id (Long) |
VERSIONABLE | Optimistic lock | version (Integer) |
NONE | Fully custom | None |
wires: [IDENTIFIABLE] // Only primary key
wires: [IDENTIFIABLE, AUDITABLE, DELETABLE] // Audit + logical delete
wires: [ALL] // Full features
wires: [NONE] // Fully custom
views (repeated View message)Warning: Do not add views unless explicitly required!
Fields:
name (string): View namedescription (string): View descriptionincludes (repeated string): Included field listexcludes (repeated string): Excluded field listreferences (repeated ReferenceView): Associated entitiesaggregated_fields (repeated AggregatedField): Aggregated fieldsviews: [{
name: "PetView",
description: "Pet list view",
includes: ["id", "name", "category", "size"]
}]
views: [{
name: "PetDetailView",
description: "Pet detail view (with category info)",
includes: ["id", "name", "category"],
references: [{
entity: "Category",
includes: ["description"],
join_type: INNER,
left_field: "category",
right_field: "name"
}]
}]
liquibase (repeated Liquibase message)Fields:
version (uint32): Version number (append only)comment (string): Change descriptiondbms (repeated DBMS enum): Target databasenegative (bool): Exclude listed dbmssql (repeated string): SQL statementsDBMS enum values: MYSQL, POSTGRESQL, ORACLE, SQLSERVER, DB2, H2, HSQLDB, SQLITE, SYBASE, INFORMIX, DERBY, MARIADB
liquibase: {
version: 1,
comment: "MySQL special index",
dbms: [MYSQL],
sql: [
"CREATE FULLTEXT INDEX idx_pet_name_ft ON PET(name)"
]
}
liquibase: {
version: 2,
comment: "Partitioned table for non-H2 databases",
dbms: [H2],
negative: true,
sql: [
"ALTER TABLE PET PARTITION BY RANGE (id)"
]
}
name (string)USER_NAME, CREATED_AT)description (string)| Field | Type | Description |
|---|---|---|
nullable | bool | Allow NULL (true) or NOT NULL (false) |
unique | bool | Unique constraint |
insertable | bool | Participate in INSERT statement |
updatable | bool | Participate in UPDATE statement |
searchable | bool | Used for WHERE condition queries |
sortable | bool | Used for ORDER BY sorting |
type (Column.Type enum)Function: Explicitly specify SQL type (override proto type inference)
| Enum Value | SQL Type | Use Case |
|---|---|---|
VARCHAR | VARCHAR | String |
CHAR | CHAR | Fixed-length character |
INTEGER | INTEGER | Integer |
BIGINT | BIGINT | Long integer |
DOUBLE | DOUBLE | Float |
NUMERIC | NUMERIC | Explicit numeric type |
DECIMAL | DECIMAL | Precise decimal |
DATE | DATE | Date |
TIME | TIME | Time |
TIMESTAMP | TIMESTAMP | Timestamp |
TIME_WITH_TIMEZONE | TIME_WITH_TIMEZONE | Time with timezone |
TIMESTAMP_WITH_TIMEZONE | TIMESTAMP_WITH_TIMEZONE | Timestamp with timezone |
BOOLEAN | BOOLEAN | Boolean |
ROWID | ROWID | Database row id |
BLOB | BLOB | Binary large object |
VARBINARY | VARBINARY | Binary column |
LONGVARBINARY | LONGVARBINARY | Large binary column |
CLOB | CLOB | Character large object |
LONGVARCHAR | LONGVARCHAR | Large text column |
length (uint32)String length (only valid for VARCHAR/CHAR): length: 255
precision / scale (uint32)DECIMAL total digits and decimal places: precision: 10, scale: 2 = DECIMAL(10,2)
enum_type (EnumType enum)| Enum Value | Persisted Content | Description |
|---|---|---|
STRING | Enum name | Default, stores "AVAILABLE" |
CODE | code field | Stores business code |
com.example.enumeration.Size size = 1 [(hope.persistence.column) = {
name: "SIZE",
enum_type: STRING,
type: VARCHAR,
length: 32
}];
id (bool)Mark as primary key.
generated_value (GeneratedValue message)Fields:
strategy (GenerationType enum): Generation strategygenerator (string): Generator name (optional)Strategy enum:
| Enum Value | Description | Use Case |
|---|---|---|
TABLE | Table sequence | Generic |
SEQUENCE | Database sequence | Oracle/PostgreSQL |
IDENTITY | Auto-increment column | MySQL/SQL Server |
UUID | UUID generation | Distributed systems |
AUTO | Auto select | Let framework decide |
int64 id = 1 [(hope.persistence.column) = {
name: "ID",
id: true,
nullable: false,
generated_value: {
strategy: UUID
}
}];
| Field | Type | Description |
|---|---|---|
transient | bool | Non-persistent field (view/calculated) |
default_value | string | Column default (SQL level) |
column_definition | string | Full custom DDL |
table | string | Specify auxiliary table |
string status = 5 [(hope.persistence.column) = {
name: "STATUS",
default_value: "ACTIVE"
}];
string full_name = 10 [(hope.persistence.column) = {
transient: true
}];
Wrong:
nullable: "false" // String, wrong!
unique: "true" // String, wrong!
Correct:
nullable: false // bool type
unique: true // bool type
Wrong (old syntax):
length: {value: 32} // Old syntax!
precision: {value: 10}
Correct:
length: 32 // uint32 type
precision: 10
scale: 2
| proto type | Default SQL | Explicit options |
|---|---|---|
string | VARCHAR | CHAR, CLOB |
int32 | INTEGER | SMALLINT |
int64 | BIGINT | BIGINT |
double | DOUBLE | DECIMAL |
float | FLOAT | FLOAT |
bool | BOOLEAN | BIT |
bytes | BLOB | VARBINARY |
| enum | VARCHAR | With enum_type |
syntax = "proto3";
package com.example.pet.domain;
import "domain/annotations.proto";
import "com/example/pet/enumeration/constants.proto";
message Pet {
option (hope.persistence.table) = {
name: "PET",
description: "Pet information table",
unique_constraints: {
name: "UK_PET_NAME_CATEGORY",
column_list: ["name", "category"]
},
indexes: {
name: "IDX_PET_NAME",
column_list: ["name"]
},
wires: [IDENTIFIABLE, AUDITABLE, DELETABLE]
};
string name = 1 [(hope.persistence.column) = {
name: "NAME",
description: "Pet name",
nullable: false,
length: 32,
type: VARCHAR
}];
string category = 3 [(hope.persistence.column) = {
name: "CATEGORY",
description: "Category (associated CATEGORY#NAME)",
nullable: false,
length: 32,
type: VARCHAR
}];
com.example.pet.enumeration.Size size = 4 [(hope.persistence.column) = {
name: "SIZE",
description: "Pet size",
nullable: false,
enum_type: STRING,
type: VARCHAR,
length: 32
}];
double weight = 5 [(hope.persistence.column) = {
name: "WEIGHT",
description: "Pet weight(kg)",
nullable: false,
type: DOUBLE
}];
}
syntax = "proto3";
package com.example.order.domain;
import "domain/annotations.proto";
import "com/example/order/enumeration/constants.proto";
message Order {
option (hope.persistence.table) = {
name: "USER_ORDER",
description: "Order table",
indexes: {
name: "IDX_ORDER_USER",
column_list: ["user_id"]
},
indexes: {
name: "IDX_ORDER_STATUS",
column_list: ["status"]
},
wires: [ALL]
};
int64 id = 1 [(hope.persistence.column) = {
name: "ID",
description: "Order ID",
id: true,
nullable: false,
generated_value: {
strategy: UUID
}
}];
int64 user_id = 2 [(hope.persistence.column) = {
name: "USER_ID",
description: "User ID",
nullable: false,
searchable: true,
type: BIGINT
}];
com.example.order.enumeration.OrderStatus status = 3 [(hope.persistence.column) = {
name: "STATUS",
description: "Order status",
nullable: false,
enum_type: CODE,
type: INTEGER
}];
double total_amount = 4 [(hope.persistence.column) = {
name: "TOTAL_AMOUNT",
description: "Order total amount",
nullable: false,
type: DECIMAL,
precision: 10,
scale: 2
}];
string remark = 5 [(hope.persistence.column) = {
name: "REMARK",
description: "Order remark",
nullable: true,
length: 500,
type: VARCHAR
}];
string user_name = 10 [(hope.persistence.column) = {
transient: true
}];
}
Wrong:
nullable: "false"
length: {value: 32}
unique: "true"
Correct:
nullable: false
length: 32
unique: true
Wrong: enum_type: ORDINAL (deprecated)
Correct: enum_type: STRING or enum_type: CODE
Wrong: Missing precision and scale
Correct: Always specify both: precision: 10, scale: 2
Only need primary key → wires: [IDENTIFIABLE]
Need audit → wires: [IDENTIFIABLE, AUDITABLE]
Need logical delete → wires: [IDENTIFIABLE, DELETABLE]
Multi-tenant → wires: [IDENTIFIABLE, TENANTABLE]
Full features → wires: [ALL]
Fully custom → wires: [NONE]
Short string (<255) → VARCHAR + length
Long text → CLOB
Integer primary key → BIGINT
UUID primary key → VARCHAR(36) or framework handling
Amount → DECIMAL(10,2)
Date → DATE / TIMESTAMP
Boolean → BOOLEAN
Enum → VARCHAR + enum_type:STRING
syntax = "proto3";
package your_package;
import "domain/annotations.proto";
message EntityName {
option (hope.persistence.table) = {
name: "TABLE_NAME",
description: "Description",
wires: [IDENTIFIABLE]
};
field_type field_name = sequence [(hope.persistence.column) = {
name: "COLUMN_NAME",
description: "Description",
nullable: false,
type: SQL_TYPE
}];
}
int64 id = 1 [(hope.persistence.column) = {
name: "ID",
id: true,
nullable: false,
generated_value: {
strategy: UUID
}
}];
double field_name = sequence [(hope.persistence.column) = {
name: "COLUMN_NAME",
nullable: false,
type: DECIMAL,
precision: 10,
scale: 2
}];