Map proto message to database table structure (JPA entity).

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


⚠️ Forbidden Rules

1. Forbid file-level option

NEVER 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. Strictly forbidden to manually add any file-level options.

2. Forbid cross-layer reference 🚫

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!

message UserEntity {
  // ❌ Forbidden to use API Request/Response types
  CreateUserRequest request = 1;  // Wrong!
  UserLoginResponse response = 2;  // 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
}

Architecture Principles: Domain layer only focuses on data persistence and is completely isolated from API layer.


1. Import and Syntax

1.1 Import

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

Note: annotations.proto automatically imports persistence.proto and apihug/protobuf/extend/common.proto

1.2 Basic Syntax

Table Level (Message)

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

  // Field definitions...
}

Column Level (Field)

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

1.3 Quick Example

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]  // Automatically add id field
  };

  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)
  • Example: name: "PET"

description (string)

  • Function: Business description of the table
  • Recommendation: Use Chinese or natural language description
  • Example: description: "Pet information table"

catalog (string)

  • Function: Database Catalog
  • Use case: Multi-database environment
  • Example: catalog: "pet_db"

schema (string)

  • Function: Database Schema
  • Use case: Oracle/PostgreSQL multi-schema environments
  • Example: schema: "public"

2.2 Constraint Definition

unique_constraints (repeated)

Type: UniqueConstraint message Function: Define unique constraints

Fields:

  • name (string): Constraint name
  • column_list (repeated string): Column name array

Example:

unique_constraints: {
  name: "UK_PET_NAME_CATEGORY",
  column_list: ["name", "category"]
}

indexes (repeated)

Type: Index message Function: Define indexes

Fields:

  • name (string): Index name
  • column_list (repeated string): Column name array
  • unique (bool): Whether unique index

Example:

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)

Supported WIRE type definitions, strictly prohibited to design similar fields, maintain consistency!!

Function: Enable platform built-in common features

Optional values:

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

Example:

// Only need primary key
wires: [IDENTIFIABLE]

// Need audit and logical delete
wires: [IDENTIFIABLE, AUDITABLE, DELETABLE]

// Fully custom
wires: [NONE]

2.4 View Definition

views (repeated View message)

Warning: ⚠️ Do not add views unless explicitly required by users!

Function: Define different views for entity (list, detail, statistics, etc.)

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 other entities
  • aggregated_fields (repeated AggregatedField): Aggregated fields

Example:

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)

Function: Execute custom SQL for specific databases

Fields:

  • version (uint32): Version number (append only, cannot modify)
  • comment (string): Change description
  • dbms (repeated DBMS enum): Target database
  • negative (bool): Whether to exclude dbms (true means exclude)
  • sql (repeated string): SQL statement array

Example:

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)
  • Example: name: "NAME"

description (string)

  • Function: Business description of the column
  • Example: description: "User name"

3.2 Constraint Fields

nullable (bool)

Type: bool Function: Whether to allow NULL

Optional values:

  • true: Allow NULL
  • false: NOT NULL

Example:

nullable: false  // NOT NULL
nullable: true   // Allow NULL

unique (bool)

Type: bool Function: Whether unique constraint

Example:

unique: true   // UNIQUE
unique: false  // Not unique

insertable (bool)

Function: Whether to participate in INSERT statement

Example:

insertable: false  // Do not participate in insert (e.g., calculated fields)

updatable (bool)

Function: Whether to participate in UPDATE statement

Example:

updatable: false  // Cannot be modified (e.g., creation time)

searchable (bool)

Function: Whether used for WHERE condition query

Example:

searchable: true  // Can be used for filtering

sortable (bool)

Function: Whether used for ORDER BY sorting

Example:

sortable: true  // Can sort

3.3 Type and Length

type (Column.Type enum)

Type: hope.persistence.Column.Type enum Function: Explicitly specify SQL type (override proto type inference)

Common values:

Enum ValueSQL TypeUse Case
VARCHARVARCHARString
CHARCHARFixed-length character
INTEGERINTEGERInteger
BIGINTBIGINTLong integer
DOUBLEDOUBLEFloat
DECIMALDECIMALPrecise decimal
DATEDATEDate
TIMETIMETime
TIMESTAMPTIMESTAMPTimestamp
BOOLEANBOOLEANBoolean
BLOBBLOBBinary large object
CLOBCLOBCharacter large object

Example:

string name = 1 [(hope.persistence.column) = {
  type: VARCHAR  // Explicitly specify VARCHAR
}];

double weight = 2 [(hope.persistence.column) = {
  type: DOUBLE  // Float
}];

length (uint32)

Type: uint32 Function: String length (only valid for VARCHAR/CHAR)

Example:

length: 32  // VARCHAR(32)

length: 255  // VARCHAR(255)

precision (uint32)

Type: uint32 Function: DECIMAL total digits

Example:

precision: 10  // DECIMAL(10, ...)

scale (uint32)

Type: uint32 Function: DECIMAL decimal digits

Example:

precision: 10,
scale: 2  // DECIMAL(10, 2)

3.4 Enum Mapping

enum_type (EnumType enum)

Type: hope.persistence.EnumType enum Function: Persistence method for enum fields

Optional values:

Enum ValuePersisted ContentDescription
STRINGEnum nameDefault value, stores like “AVAILABLE”
CODEcode fieldStores business code (needs to define code in enum)

Forbidden: ❌ No longer supports ORDINAL (sequence number), deprecated due to errors

Example:

import "com/example/enumeration/constants.proto";

com.example.enumeration.Size size = 1 [(hope.persistence.column) = {
  name: "SIZE",
  enum_type: STRING,  // Store "SMALL"/"MEDIUM"/"LARGE"
  type: VARCHAR
}];

com.example.enumeration.Status status = 2 [(hope.persistence.column) = {
  name: "STATUS",
  enum_type: CODE,  // Store 1/2/3 (enum code values)
  type: INTEGER
}];

3.5 Primary Key and Auto-increment

id (bool)

Type: bool Function: Mark as primary key

Example:

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

generated_value (GeneratedValue message)

Function: Primary key generation strategy

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

Example:

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

int64 order_id = 1 [(hope.persistence.column) = {
  name: "ORDER_ID",
  id: TRUE,
  generated_value: {
    strategy: SEQUENCE,
    generator: "order_seq"
  }
}];

3.6 Advanced Fields

transient (bool)

Function: Mark as non-persistent field (view or calculated field only)

Example:

string full_name = 10 [(hope.persistence.column) = {
  transient: true  // Do not map to database
}];

default_value (string)

Function: Column default value (SQL level)

Example:

string status = 5 [(hope.persistence.column) = {
  name: "STATUS",
  default_value: "ACTIVE"
}];

int32 retry_count = 6 [(hope.persistence.column) = {
  name: "RETRY_COUNT",
  default_value: "0"
}];

column_definition (string)

Function: Fully customize column DDL (override all auto-generation)

Warning: ⚠️ Use only for special needs

Example:

string name = 1 [(hope.persistence.column) = {
  column_definition: "VARCHAR(32) NOT NULL COMMENT 'Name'"
}];

table (string)

Function: Specify which table the field belongs to (auxiliary table scenario)

Example:

string extra_info = 10 [(hope.persistence.column) = {
  name: "EXTRA_INFO",
  table: "PET_DETAIL"  // Field in PET_DETAIL table
}];

4. Key Type Identification

4.1 bool vs string

Wrong: ❌ The following syntax is wrong

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

Correct: ✅ Must use bool type

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

4.2 uint32 vs wrapper type

Wrong: ❌ The following syntax is outdated

length: {value: 32}  // Old syntax, no longer needed!
precision: {value: 10}  // Old syntax, no longer needed!

Correct: ✅ Use uint32 directly

length: 32  // uint32 type

precision: 10

scale: 2

4.3 Column.Type vs proto type

proto field type vs SQL type mapping:

proto typeDefault SQLExplicit type specification
stringVARCHARtype: VARCHAR / CHAR / CLOB
int32INTEGERtype: INTEGER / SMALLINT
int64BIGINTtype: BIGINT
doubleDOUBLEtype: DOUBLE / DECIMAL
floatFLOATtype: FLOAT
boolBOOLEANtype: BOOLEAN / BIT
bytesBLOBtype: BLOB / VARBINARY
enumVARCHARtype: VARCHAR (with enum_type)

Example:

// proto string, default VARCHAR
string name = 1 [(hope.persistence.column) = {
  name: "NAME"
  // No type specified, auto-inferred as VARCHAR
}];

// proto string, force CHAR
string code = 2 [(hope.persistence.column) = {
  name: "CODE",
  type: CHAR,  // Explicitly specify CHAR
  length: 10
}];

// proto double, force DECIMAL
double price = 3 [(hope.persistence.column) = {
  name: "PRICE",
  type: DECIMAL,  // Explicitly specify DECIMAL
  precision: 10,
  scale: 2
}];

5. Complete Examples

5.1 Basic Entity: Pet Table

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,  // Store "SMALL"/"MEDIUM"/"LARGE"
    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

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]  // Enable all features
  };

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

  // Foreign key
  int64 user_id = 2 [(hope.persistence.column) = {
    name: "USER_ID",
    description: "User ID",
    nullable: FALSE,
    searchable: TRUE,
    type: BIGINT
  }];

  // Enum (store code)
  com.example.order.enumeration.OrderStatus status = 3 [(hope.persistence.column) = {
    name: "STATUS",
    description: "Order status",
    nullable: FALSE,
    enum_type: CODE,  // Store 1/2/3... (code value)
    type: INTEGER
  }];

  // Amount (DECIMAL)
  double total_amount = 4 [(hope.persistence.column) = {
    name: "TOTAL_AMOUNT",
    description: "Order total amount",
    nullable: FALSE,
    type: DECIMAL,
    precision: 10,
    scale: 2
  }];

  // Remark (nullable long text)
  string remark = 5 [(hope.persistence.column) = {
    name: "REMARK",
    description: "Order remark",
    nullable: TRUE,
    length: 500,
    type: VARCHAR
  }];

  // Non-persistent calculated field
  string user_name = 10 [(hope.persistence.column) = {
    transient: TRUE
  }];
}

5.3 Entity with Views

message Pet {
  option (hope.persistence.table) = {
    name: "PET",
    description: "Pet",

    // List view
    views: [{
      name: "PetListView",
      description: "Pet list view",
      includes: ["id", "name", "category", "size"]
    }],

    // Detail view (with category)
    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"
      }]
    }],

    wires: [IDENTIFIABLE]
  };

  // Field definitions...
}

6. Common Errors

6.1 Type Confusion

Wrong:

nullable: "false"  // String, wrong!
length: {value: 32}  // Old syntax, no longer needed!
unique: "true"     // String, wrong!

Correct:

nullable: false  // bool type
length: 32  // uint32 type
unique: true     // bool type

6.2 Enum Mapping Error

Wrong:

com.example.Size size = 1 [(hope.persistence.column) = {
  name: "SIZE",
  enum_type: ORDINAL,  // ORDINAL is deprecated!
  type: INTEGER
}];

Correct:

com.example.Size size = 1 [(hope.persistence.column) = {
  name: "SIZE",
  enum_type: STRING,  // Use STRING or CODE
  type: VARCHAR,
  length: 32
}];

6.3 DECIMAL Configuration Error

Wrong:

double price = 1 [(hope.persistence.column) = {
  type: DECIMAL  // Missing precision and scale
}];

Correct:

double price = 1 [(hope.persistence.column) = {
  type: DECIMAL,
  precision: 10,
  scale: 2
}];

6.4 Incomplete Primary Key Configuration

Wrong:

int64 id = 1 [(hope.persistence.column) = {
  id: TRUE
  // Missing nullable and generated_value
}];

Correct:

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

7. Best Practices

7.1 Table and Column Naming Conventions

  • Table name: Uppercase underscore (USER_ORDER, PET_CATEGORY)
  • Column name: Uppercase underscore (USER_ID, CREATED_AT)
  • Index name: IDX_table_column (IDX_PET_NAME)
  • Unique constraint: UK_table_column (UK_USER_EMAIL)

7.2 Wire Selection Guide

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.3 Type Selection Guide

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 → TIMESTAMP
Boolean → BOOLEAN
Enum → VARCHAR + enum_type:STRING

7.4 Index Design Recommendations

  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

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

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

Enum Field Template

import "your_enum_path/constants.proto";

your_package.EnumName field_name = sequence [(hope.persistence.column) = {
  name: "COLUMN_NAME",
  nullable: FALSE,
  enum_type: STRING,
  type: VARCHAR,
  length: 32
}];

DECIMAL Template

double field_name = sequence [(hope.persistence.column) = {
  name: "COLUMN_NAME",
  nullable: FALSE,
  type: DECIMAL,
  precision: 10,
  scale: 2
}];