The appget.dev/java project uses a schema-first, protobuf-first approach to code generation. Business rules are defined in human-friendly Gherkin .feature files (the source of truth for rules), while SQL remains the source of truth for domain models. The pipeline converts .feature files + metadata.yaml into specs.yaml, SQL schemas into .proto files, then uses protoc to generate Java protobuf model classes. It supports SQL views, generic specifications with descriptor-based evaluation, compound AND/OR conditions, metadata-aware authorization rules, gRPC service stubs, and proto-first OpenAPI generation.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Gherkin + Schema-First, Protobuf-First Code Generation β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
LAYER 0: HUMAN-FRIENDLY BUSINESS RULES (Source of Truth)
ββββββββββββββββββββββββ βββββββββββββββββββ
β features/*.feature β β metadata.yaml β
β (Gherkin BDD rules) β β (context POJOs)β
ββββββββββββ¬ββββββββββββ ββββββββββ¬βββββββββ
β β
β FeatureToSpecsConverter
β (Gherkin parser + YAML assembly)
β
βββββββββββββββββββ
β specs.yaml β β GENERATED (git-ignored)
β (rules + meta) β
βββββββ¬ββββββββ¬ββββ
β β
β β
β βββββββββββββββββββββββββββββββββββββββββββ
β β
LAYER 1: SCHEMA SOURCE OF TRUTH β
βββββββββββββββββββ βββββββββββββββββββ β
β schema.sql β β views.sql β β
β (SQL DDL) β β (SQL views) β β
ββββββββββ¬βββββββββ ββββββββββ¬βββββββββ β
β β β
ββββββββββ¬ββββββββββββ β
β β
β SQLSchemaParser β
β (regex-based, multi-dialect) β
β β
LAYER 2: INTERMEDIATE REPRESENTATION β
βββββββββββββββββββ β
β models.yaml β β Auto-generated β
ββββββββββ¬βββββββββ β
β β
β ModelsToProtoConverter βββββββββββββββββββββββ€
β (models.yaml + specs β .proto with rules) β
β β
βββββββββββββββββββββββββββββββββββ β
β .proto files (per domain) β β
β appget_models.proto β β
β hr_models.proto β β
β finance_models.proto β β
β *_views.proto, *_services.protoβ β
β rules.proto (custom options) β β
ββββββββββ¬βββββββββββββββββββββββββ β
β β
ββββββ΄βββββββββββββββ β
β β β
β protoc β ProtoOpenAPIGenerator β
β (protobuf β (proto-first REST) β
β compiler) β β
β β β
βββββββββββββββββββ ββββββββββββββββββββ β
β Java Protobuf β β openapi.yaml β β
β Models + Views β β (REST spec) β β
β gRPC Stubs β ββββββββββββββββββββ β
β (MessageOrBuilder) β
ββββββββββ¬βββββββββββββββββββββββββ β
β β β
β (models.yaml) β (parallel) β
ββββββββββ βββββββββ β
β β β
βββββββββ€ SpecificationGenerator ββββββ
β (specs.yaml + models.yaml β Java specs)
β
LAYER 3: SPECIFICATIONS + METADATA
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Generated specification classes β
β βββ UserEmailValidation.java (simple condition) β
β βββ UserAccountStatus.java (compound AND) β
β βββ HighEngagementPost.java (view-targeting) β
β βββ AdminAuthorizationRequired.java (metadata-required)β
β βββ ContentTargetValidation.java (compound OR) β
β β
β Generated metadata POJOs (Lombok) β
β βββ SsoContext.java β
β βββ RolesContext.java β
β βββ TenantContext.java β
ββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ
β
β DescriptorRegistry + RuleInterceptor + DefaultDataBuilder
LAYER 4: RUNTIME EVALUATION (Descriptor-Based)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Descriptor-driven business logic evaluation β
β - DescriptorRegistry: dynamic model discovery β
β - RuleInterceptor: loads rules from .proto custom opts β
β - Specification: protobuf getField() for models/views β
β - Specification: reflection fallback for metadata POJOs β
β - Compound rules: AND/OR logic β
β - Metadata rules: authorization checks before evaluation β
β - DefaultDataBuilder: DynamicMessage-based sample data β
β Result: APPROVED / REJECTED / SENIOR_MANAGER / etc. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
make features-to-specs # Convert .feature files + metadata.yaml -> specs.yaml
make parse-schema # Parse schema.sql + views.sql -> generate models.yaml
make generate-proto # (schema.sql + specs.yaml) -> .proto -> protoc -> Java model classes
make generate-specs # Parse specs.yaml + models.yaml -> generate specs + metadata POJOs
make generate-registry # Generate DescriptorRegistry from models.yaml
make generate-openapi # .proto files -> generate openapi.yaml (ProtoOpenAPIGenerator)
make generate # Generate all (features-to-specs + protoc + specs + registry + openapi)
make generate-server # Generate Spring Boot server from models and specs
make build # Full pipeline: parse schema, generate, compile
make test # Run all 280 tests
make run # Build and execute the application
make run-server # Build and run the Spring Boot server
make clean # Clean all build artifacts
make all # clean -> generate -> test -> buildgradle compileGenerators # Compile generator classes only
gradle featuresToSpecs # Run FeatureToSpecsConverter (features + metadata -> specs.yaml)
gradle parseSchema # Run SQLSchemaParser (schema.sql + views.sql -> models.yaml)
gradle generateProto # Run ModelsToProtoConverter + protoc (models.yaml -> .proto -> Java)
gradle generateSpecs # Run SpecificationGenerator (specs.yaml + models.yaml -> Java)
gradle generateDescriptorRegistry # Run DescriptorRegistryGenerator (models.yaml -> DescriptorRegistry)
gradle generateOpenAPI # Run ProtoOpenAPIGenerator (.proto -> openapi.yaml)
gradle compileJava # Compile all (main + generated)
gradle test # Run all src/tests
gradle build # Full build with packagingcompileGenerators (independent)
β
ββ featuresToSpecs (features/*.feature + metadata.yaml -> specs.yaml)
β β
ββ parseSchema (schema.sql + views.sql -> models.yaml)
β β
ββ modelsToProto (depends on: parseSchema + featuresToSpecs + compileGenerators)
β β
β generateProto (protoc: .proto -> Java model classes)
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β
β generateSpecs generateOpenAPI
β β (depends on: featuresToSpecs + parseSchema) β (depends on: modelsToProto)
ββ generateDescriptorRegistry (models.yaml -> DescriptorRegistry.java)
β β (depends on: parseSchema)
compileJava
β (depends on: generateSpecs, generateDescriptorRegistry, generateProto)
test
β (depends on: classes)
build
Critical Rule: modelsToProto depends on parseSchema (models.yaml must exist) and featuresToSpecs (specs.yaml must exist). generateProto depends on modelsToProto, NOT on classes. This breaks the circular dependency.
-
features/*.feature - Gherkin business rule definitions (human-friendly BDD format)
- One
.featurefile per domain (admin.feature,auth.feature,social.feature) - Feature-level tags:
@domain:authassigns domain to all scenarios - Scenario-level tags:
@target:users,@rule:RuleName,@blocking,@view - Step patterns:
When <field> <operator_phrase> <value>,Then status is "<value>",But otherwise status is "<value>" - Compound conditions:
When all conditions are met:(AND) /When any condition is met:(OR) + data table - Metadata requirements:
Given <category> context requires:+ data table
- One
-
metadata.yaml - Context POJO definitions (extracted from old specs.yaml metadata section)
- Defines authorization POJOs: sso, roles, user, location
- Committed separately from rules (rules live in
.featurefiles)
-
schema.sql - SQL DDL statements (CREATE TABLE)
- Supports multiple database dialects (MySQL, SQLite, Oracle, MSSQL, PostgreSQL)
- Tables mapped to domains via
DOMAIN_MAPPINGin SQLSchemaParser - Column naming: SQL
role_id(snake_case) β models.yaml storesrole_id(snake_case, language-agnostic); proto field names arerole_id(snake_case direct); Java getters aregetRoleId()(camelCase, from protobuf) - Nullability:
NOT NULL-> non-nullable types; nullable -> wrapper types
-
views.sql - SQL CREATE VIEW statements
- Column types resolved from source table definitions
- Aliases mapped to source tables (e.g.,
p->posts) - Aggregate functions supported (COUNT -> long, SUM -> BigDecimal, AVG -> double)
- Views mapped to domains via
VIEW_DOMAIN_MAPPING
- specs.yaml - Generated from
features/*.feature+metadata.yamlby FeatureToSpecsConvertermetadata:section from metadata.yaml (context POJOs)rules:section from feature files (Gherkin scenarios β YAML rules)- Conditions use snake_case for proto model/view fields (
role_id,salary_amount) - Metadata
requires:conditions use camelCase (roleLevel,authenticated) for Lombok POJO reflection
- Use
treeif installed, elsels -lRwill be good enough
tree src/main/java-generated/| SQL Type | Java Type | Nullable | Notes |
|---|---|---|---|
| VARCHAR, CHAR, TEXT | String | String | Unicode text |
| INT, INTEGER, SMALLINT | int | Integer | 32-bit integer |
| BIGINT, LONG | long | Long | 64-bit integer |
| DECIMAL, NUMERIC | BigDecimal | BigDecimal | Precise decimals |
| FLOAT, DOUBLE, REAL | double | Double | 64-bit floating point |
| DATE | LocalDate | LocalDate | Date without time |
| TIMESTAMP, DATETIME | LocalDateTime | LocalDateTime | Date and time |
| BOOLEAN, BOOL | boolean | Boolean | True/false |
| SQL Function | Java Type |
|---|---|
| COUNT(*) | long |
| SUM(x) | BigDecimal |
| AVG(x) | double |
| MIN(x) / MAX(x) | Same as source column |
Tables and views are grouped into domains:
-- auth domain β users, sessions
-- social domain β posts, comments, likes, follows, reposts
-- admin domain β moderation_flags
Views (-- social domain):
user_profile_view, post_detail_view, comment_detail_view, feed_post_view
Namespace Convention:
- Domain
authβdev.appget.auth.model - Domain
socialβdev.appget.social.model/dev.appget.social.view - Domain
adminβdev.appget.admin.model
Simple (single condition):
conditions:
- field: age
operator: ">"
value: 18Compound (AND/OR):
conditions:
operator: AND
clauses:
- field: age
operator: ">="
value: 30
- field: role_id
operator: "=="
value: "Manager"Metadata fields use camelCase (matching Lombok POJO getters):
requires:
sso:
- field: authenticated
operator: "=="
value: true
roles:
- field: roleLevel
operator: ">="
value: 3target:
type: model # or "view"
name: users # model/view name (snake_case plural, matches models.yaml)
domain: auth # domain for import resolution- Gherkin 38.0.0 - Cucumber Gherkin parser for
.featurefiles - Handlebars.java 4.5.0 - Template engine for structural code generation (selective use)
- Protobuf 3.25.3 - Protocol Buffers (protoc compiler + Java runtime)
- gRPC-Java 1.62.2 - gRPC service stubs (protoc-gen-grpc-java)
- JSQLParser 5.3 - SQL parsing and multi-dialect support
- SnakeYAML 2.2 - YAML processing
- Lombok 1.18.38 - Metadata POJO annotations (SsoContext, RolesContext, etc.)
- Log4j2 2.23.1 - Logging
- JUnit 5 5.11.3 - Testing framework (280 tests)
Generators use two approaches, chosen based on output complexity:
Handlebars .hbs templates (structural output with variable slots):
| Generator | Template |
|---|---|
DescriptorRegistryGenerator |
templates/descriptor/DescriptorRegistry.java.hbs |
SpecificationGenerator |
templates/specification/SimpleSpecification.java.hbs |
SpecificationGenerator |
templates/specification/CompoundSpecification.java.hbs |
SpecificationGenerator |
templates/specification/MetadataPojo.java.hbs |
StringBuilder (complex conditional logic):
| Generator | Output |
|---|---|
AppServerGenerator |
Spring Boot REST API (controllers, services, repos) |
ProtoOpenAPIGenerator |
OpenAPI 3.0 YAML |
ModelsToProtoConverter |
.proto files |
OpenAPIDefaultScriptGenerator |
Default scripts |
SQLSchemaParser |
models.yaml |
FeatureToSpecsConverter |
specs.yaml |
Note on {{{triple-braces}}}: Handlebars HTML-escapes {{var}} by default (e.g., > becomes >). Since we generate Java code (not HTML), templates use {{{var}}} (triple-brace, raw output) for any value containing operators, generics, or special characters. This is a known maintenance gotcha β future template authors must use {{{ for code values.
Supporting classes:
TemplateEngine.java- Handlebars wrapper with file-based.hbsloader and registered helpers (lowerFirst, capitalize, camelToKebab, camelToSnake)CodeGenUtils.java- Shared utility methods for string transforms, parenthesis matching, smart splitting
Template files live in src/main/resources/templates/ with .hbs extension.
- Gherkin Business Rules: Human-friendly
.featurefiles as source of truth for business rules (BDD format) - Feature-to-Specs Conversion:
FeatureToSpecsConverterparses Gherkin β generatesspecs.yaml(intermediate representation) - Multi-Dialect Support: Regex-based parsing handles MySQL, SQLite, Oracle, MSSQL, PostgreSQL
- SQL View Parsing: Resolves column types from source tables via alias mapping
- Name Preservation: table/column names kept as snake_case (generators apply
snakeToPascal()for language-specific output) - Protobuf-First Models: SQL β .proto β protoc β Java protobuf classes (MessageOrBuilder)
- Proto-First OpenAPI: .proto files β OpenAPI 3.0 YAML (full CRUD, security)
- gRPC Service Stubs: 5 services across 3 domains (protoc-gen-grpc-java)
- Domain Isolation: Multiple domains prevent naming conflicts
- Descriptor-Based Evaluation: Protobuf
getField()API for models/views (no reflection) - Dual-Path Specification: Descriptor API for protobuf, reflection fallback for Lombok POJOs
- Compound Conditions: AND/OR logic for multi-field business rules
- Metadata Authorization: Rules require authentication/role context before evaluation
- Descriptor Registry: Dynamic model discovery via auto-generated protobuf descriptor registry (from models.yaml)
- Rule Interceptor: Reads business rules from .proto custom options at runtime
- Test Data Builder: DynamicMessage-based sample data generation