Spec

Database Directive Specification

Database Modeling Extension User Manual — map proto messages to database table structures (entities).

Map proto message to database table structure (entity).

  • Extension Package: apihug/protobuf/domain/annotations.proto
  • Scope: Message(table) + Field(column)
  • Scenarios: entity, DDL generation, Liquibase

Forbidden Rules

1. Forbid file-level option

NEVER add any file-level option declarations in proto files:

Proto
// 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.

2. Forbid cross-layer reference

Domain layer (domain/) must maintain architectural purity:

Proto
// Forbidden to reference API layer messages
import "com/example/api/user/api.proto";  // Wrong!
import "com/example/api/order/request.proto";  // Wrong!

Allowed references:

Proto
// 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
}

1. Import and Syntax

1.1 Import

Proto
import "apihug/protobuf/domain/annotations.proto";

Note: annotations.proto imports persistence.proto, which in turn imports view.proto for entity view definitions.

1.2 Extension Points (from source)

From apihug/protobuf/domain/annotations.proto:

Proto
extend google.protobuf.MessageOptions {
  Table table = 1772;
}

extend google.protobuf.FieldOptions {
  Column column = 1773;
}

1.3 Basic Syntax

Table Level (Message):

Proto
message EntityName {
  option (hope.persistence.table) = {
    name: "TableName";
    description: "Table description";
    // Other configurations...
  };
}

Column Level (Field):

Proto
string field_name = sequence [(hope.persistence.column) = {
  name: "ColumnName";
  description: "Column description";
  nullable: false;
  type: VARCHAR;
}];

1.4 Quick Example

Proto
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
  }];
}

2. Table Extension Details

2.1 Basic Fields

name (string)

  • Function: Database table name
  • Recommendation: Use uppercase underscore style (CATEGORY, USER_ORDER)

description (string)

  • Function: Business description of the table

catalog (string)

  • Function: Database Catalog (multi-database environments)

schema (string)

  • Function: Database Schema (Oracle/PostgreSQL)

2.2 Constraint Definition

unique_constraints (repeated UniqueConstraint)

Fields:

  • name (string): Constraint name
  • column_list (repeated string): Column name array
Proto
unique_constraints: {
  name: "UK_PET_NAME_CATEGORY",
  column_list: ["name", "category"]
}

indexes (repeated Index)

Fields:

  • name (string): Index name
  • column_list (repeated string): Column name array
  • unique (bool): Whether unique index
Proto
indexes: {
  name: "IDX_PET_NAME",
  column_list: ["name"]
}

indexes: {
  name: "IDX_CATEGORY_UNIQUE",
  column_list: ["category"],
  unique: true
}

2.3 Built-in Features (Wire Protocol)

wires (repeated Wire enum)

Function: Enable platform built-in common features

Enum ValueMeaningAuto-added Fields
ALLEnable all featuresid + audit + delete flag + tenant + version
IDENTIFIABLEOnly add primary keyid (Long)
AUDITABLEAudit fieldscreated_at, updated_at, created_by, updated_by
DELETABLELogical deletedeleted (Boolean)
TENANTABLEMulti-tenanttenant_id (Long)
VERSIONABLEOptimistic lockversion (Integer)
NONEFully customNone
Proto
wires: [IDENTIFIABLE]                          // Only primary key
wires: [IDENTIFIABLE, AUDITABLE, DELETABLE]    // Audit + logical delete
wires: [ALL]                                   // Full features
wires: [NONE]                                  // Fully custom

2.4 View Definition

views (repeated View message)

Warning: Do not add views unless explicitly required!

Fields:

  • name (string): View name
  • description (string): View description
  • includes (repeated string): Included field list
  • excludes (repeated string): Excluded field list
  • references (repeated ReferenceView): Associated entities
  • aggregated_fields (repeated AggregatedField): Aggregated fields
Proto
views: [{
  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"
  }]
}]

2.5 Liquibase Integration

liquibase (repeated Liquibase message)

Fields:

  • version (uint32): Version number (append only)
  • comment (string): Change description
  • dbms (repeated DBMS enum): Target database
  • negative (bool): Exclude listed dbms
  • sql (repeated string): SQL statements

DBMS enum values: MYSQL, POSTGRESQL, ORACLE, SQLSERVER, DB2, H2, HSQLDB, SQLITE, SYBASE, INFORMIX, DERBY, MARIADB

Proto
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)"
  ]
}

3. Column Extension Details

3.1 Basic Fields

name (string)

  • Function: Database column name
  • Recommendation: Uppercase underscore style (USER_NAME, CREATED_AT)

description (string)

  • Function: Business description of the column

3.2 Constraint Fields

FieldTypeDescription
nullableboolAllow NULL (true) or NOT NULL (false)
uniqueboolUnique constraint
insertableboolParticipate in INSERT statement
updatableboolParticipate in UPDATE statement
searchableboolUsed for WHERE condition queries
sortableboolUsed for ORDER BY sorting

3.3 Type and Length

type (Column.Type enum)

Function: Explicitly specify SQL type (override proto type inference)

Enum ValueSQL TypeUse Case
VARCHARVARCHARString
CHARCHARFixed-length character
INTEGERINTEGERInteger
BIGINTBIGINTLong integer
DOUBLEDOUBLEFloat
NUMERICNUMERICExplicit numeric type
DECIMALDECIMALPrecise decimal
DATEDATEDate
TIMETIMETime
TIMESTAMPTIMESTAMPTimestamp
TIME_WITH_TIMEZONETIME_WITH_TIMEZONETime with timezone
TIMESTAMP_WITH_TIMEZONETIMESTAMP_WITH_TIMEZONETimestamp with timezone
BOOLEANBOOLEANBoolean
ROWIDROWIDDatabase row id
BLOBBLOBBinary large object
VARBINARYVARBINARYBinary column
LONGVARBINARYLONGVARBINARYLarge binary column
CLOBCLOBCharacter large object
LONGVARCHARLONGVARCHARLarge 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)

3.4 Enum Mapping

enum_type (EnumType enum)

Enum ValuePersisted ContentDescription
STRINGEnum nameDefault, stores "AVAILABLE"
CODEcode fieldStores business code
Proto
com.example.enumeration.Size size = 1 [(hope.persistence.column) = {
  name: "SIZE",
  enum_type: STRING,
  type: VARCHAR,
  length: 32
}];

3.5 Primary Key and Auto-increment

id (bool)

Mark as primary key.

generated_value (GeneratedValue message)

Fields:

  • strategy (GenerationType enum): Generation strategy
  • generator (string): Generator name (optional)

Strategy enum:

Enum ValueDescriptionUse Case
TABLETable sequenceGeneric
SEQUENCEDatabase sequenceOracle/PostgreSQL
IDENTITYAuto-increment columnMySQL/SQL Server
UUIDUUID generationDistributed systems
AUTOAuto selectLet framework decide
Proto
int64 id = 1 [(hope.persistence.column) = {
  name: "ID",
  id: true,
  nullable: false,
  generated_value: {
    strategy: UUID
  }
}];

3.6 Advanced Fields

FieldTypeDescription
transientboolNon-persistent field (view/calculated)
default_valuestringColumn default (SQL level)
column_definitionstringFull custom DDL
tablestringSpecify auxiliary table
Proto
string status = 5 [(hope.persistence.column) = {
  name: "STATUS",
  default_value: "ACTIVE"
}];

string full_name = 10 [(hope.persistence.column) = {
  transient: true
}];

4. Key Type Identification

4.1 bool vs string

Wrong:

Proto
nullable: "false"  // String, wrong!
unique: "true"     // String, wrong!

Correct:

Proto
nullable: false  // bool type
unique: true     // bool type

4.2 uint32 vs wrapper type

Wrong (old syntax):

Proto
length: {value: 32}  // Old syntax!
precision: {value: 10}

Correct:

Proto
length: 32  // uint32 type
precision: 10
scale: 2

4.3 Proto type to SQL type mapping

proto typeDefault SQLExplicit options
stringVARCHARCHAR, CLOB
int32INTEGERSMALLINT
int64BIGINTBIGINT
doubleDOUBLEDECIMAL
floatFLOATFLOAT
boolBOOLEANBIT
bytesBLOBVARBINARY
enumVARCHARWith enum_type

5. Complete Examples

5.1 Basic Entity: Pet Table

Proto
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
  }];
}

5.2 Complex Entity: Order Table

Proto
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
  }];
}

6. Common Errors

6.1 Type Confusion

Wrong:

Proto
nullable: "false"
length: {value: 32}
unique: "true"

Correct:

Proto
nullable: false
length: 32
unique: true

6.2 Enum Mapping Error

Wrong: enum_type: ORDINAL (deprecated) Correct: enum_type: STRING or enum_type: CODE

6.3 DECIMAL Configuration

Wrong: Missing precision and scale Correct: Always specify both: precision: 10, scale: 2


7. Best Practices

7.1 Wire Selection Guide

Code
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]

7.2 Type Selection Guide

Code
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

7.3 Index Design

  1. Add index for foreign keys
  2. Add index for frequently queried fields
  3. Pay attention to order in composite indexes (leftmost prefix)
  4. Avoid too many indexes (affects write performance)

8. Quick Reference

Basic Template

Proto
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
  }];
}

Primary Key Template

Proto
int64 id = 1 [(hope.persistence.column) = {
  name: "ID",
  id: true,
  nullable: false,
  generated_value: {
    strategy: UUID
  }
}];

DECIMAL Template

Proto
double field_name = sequence [(hope.persistence.column) = {
  name: "COLUMN_NAME",
  nullable: false,
  type: DECIMAL,
  precision: 10,
  scale: 2
}];
Copyright © 2026 ApiHug·AI-native Enterprise Architecture Factory