Write your database schema in SQL and business rules in Gherkin. Automatically generate Java models, validation logic, and REST API endpoints.
Production-ready code generation system: Gherkin-first business rules, schema-first protobuf design with SQL views, descriptor-based rule evaluation, compound business rules (AND/OR), metadata-aware authorization with toggle-based category registry, blocking/informational rule enforcement, and over 290 comprehensive unit tests.
appget.dev/java is a code generator that converts your database schema into a complete Java backend with:
- Protobuf Models - Auto-generated from
schema.sqlvia.protofiles (14 models + 10 views across 3 domains) - gRPC Service Stubs - Services across 3 domains with full CRUD
- REST API Server - Spring Boot server with CRUD endpoints for all your models
- Business Rules - Define rules in Gherkin
.featurefiles, converted tospecs.yaml, enforced automatically via generated Specification classes - Authorization - Rules can check headers/metadata to enforce access control
- Type Safety - Everything generated from same schema source = perfect type alignment
Think of it like: Gherkin rules + SQL schema → Protocol Buffers → gRPC + REST + Business Rules, all from human-friendly sources of truth.
YOU WRITE THIS: SYSTEM GENERATES THIS:
┌──────────────────────┐
│ features/*.feature │ ┌──────────────────────────────┐
│ (Business rules) │──┐ │ specs.yaml (intermediate) │
└──────────────────────┘ ├──▶ │ (YAML rules + metadata) │
┌──────────────────────┐ │ └──────────────────────────────┘
│ metadata.yaml │──┘ │
│ (Context POJOs) │ │
└──────────────────────┘ │
▼
┌──────────────────┐ ┌──────────────────────────────┐
│ schema.sql │ │ Java Domain Models │
│ (Your DB) │────────▶│ (Users, Posts, ModerationActions, etc) │
└──────────────────┘ └──────────────────────────────┘
│
┌──────────────────┐ │
│ views.sql │─────────────────┘
│ (Read models) │ ┌──────────────────────────────┐
└──────────────────┘────────▶│ REST API Specification │
│ (OpenAPI 3.0) │
│
┌────────┘
▼
┌──────────────────────────────┐
│ Spring Boot REST Server │
│ - Controllers (endpoints) │
│ - Services (rules + logic) │
│ - Repositories (storage) │
└──────────────────────────────┘
| File | Purpose | Edit When |
|---|---|---|
| features/*.feature | Business rules in Gherkin (BDD) | Adding or changing validation logic |
| metadata.yaml | Curated metadata registry with toggle model (14 built-in categories) | Enabling/disabling categories or adding custom ones |
| schema.sql | Database table definitions | Adding new models or tables |
| views.sql | SQL composite views (read models) | Creating read-optimized views |
| File/Directory | What It Is | Generated From |
|---|---|---|
| specs.yaml | Intermediate rules + metadata YAML | features/*.feature + metadata.yaml |
| models.yaml | Intermediate YAML representation | schema.sql + views.sql |
| openapi.yaml | REST API contract specification | models.yaml |
| src/main/java-generated/ | All Java models, views, specs | models.yaml + specs.yaml |
| generated-server/ | Complete Spring Boot REST server | models.yaml + specs.yaml |
# 1. Generate everything from existing files (already in repo)
make all
# This runs: clean → generate (features-to-specs + proto + specs) → test → build
# Time: ~5 seconds
# Result: All 290+ tests pass
# 2. Run the rule engine demo
make run
# See rules evaluated on sample Users object
# Output shows which rules passed/failed
# 3. View test results
open build/reports/tests/test/index.html
# Detailed test report in your browserThat's it! You now have a working system. The models, rules, and specifications are already defined in the repo.
This is where you define all your models. Edit schema.sql:
-- auth domain
CREATE TABLE users (
id VARCHAR(50) NOT NULL,
username VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
is_verified BOOLEAN NOT NULL,
is_active BOOLEAN NOT NULL
);
-- social domain
CREATE TABLE posts (
id VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
like_count INT NOT NULL,
is_public BOOLEAN NOT NULL,
is_deleted BOOLEAN NOT NULL
);
-- admin domain
CREATE TABLE moderation_actions (
id VARCHAR(50) NOT NULL,
reason VARCHAR(255) NOT NULL,
is_active BOOLEAN NOT NULL
);Domains assigned from SQL comments (-- <domain> domain before each table group):
users→dev.appget.auth.model.Usersposts→dev.appget.social.model.Postsmoderation_actions→dev.appget.admin.model.ModerationActions
Current domain breakdown (14 models + 10 views across 3 domains):
| Domain | Models | Views |
|---|---|---|
| admin | roles, user_roles, moderation_actions, company_settings | user_role_view, moderation_queue_view, company_health_view |
| auth | users, oauth_providers, oauth_tokens, api_keys, sessions | user_oauth_view, api_key_stats_view |
| social | posts, comments, likes, follows, feeds | post_detail_view, comment_detail_view, user_feed_view, user_stats_view, trending_posts_view |
Create composite views for reporting/read-optimization:
-- social domain
CREATE VIEW post_detail_view AS
SELECT
p.id AS post_id,
p.content AS post_content,
p.like_count AS like_count,
u.username AS author_username,
u.is_verified AS author_verified
FROM posts p
JOIN users u ON p.author_id = u.id;Views generate Java view classes automatically. System resolves column types from source tables.
make parse-schema
# INPUT: schema.sql + views.sql
# OUTPUT: models.yaml (with all fields, types, domains)What happens: SQLSchemaParser reads your SQL, parses tables/views, maps types to Java, generates models.yaml.
make generate-proto
# INPUT: schema.sql + views.sql + specs.yaml (for rule embedding)
# OUTPUT: .proto files → protoc → build/generated/
# ├── dev/appget/auth/model/Users.java (protobuf)
# ├── dev/appget/social/model/Posts.java (protobuf)
# ├── dev/appget/admin/model/ModerationActions.java (protobuf)
# ├── dev/appget/social/view/PostDetailView.java (protobuf)
# └── gRPC service stubs (8 services)All models are protobuf MessageOrBuilder classes with Builder pattern.
This is where you write authorization and validation logic using human-friendly Gherkin syntax.
Edit features/auth.feature:
@domain:auth
Feature: Auth Domain Business Rules
@target:users @blocking @rule:UserActivationCheck
Scenario: User account must be active
When is_active equals true
Then status is "ACCOUNT_ACTIVE"
But otherwise status is "ACCOUNT_INACTIVE"
@target:users @rule:UserVerificationStatus
Scenario: User can be verified badge holder
When is_verified equals true
Then status is "VERIFIED_USER"
But otherwise status is "UNVERIFIED_USER"
@target:users @blocking @rule:AdminAuthenticationRequired
Scenario: User with admin role can manage system
Given roles context requires:
| field | operator | value |
| role_level | >= | 3 |
And sso context requires:
| field | operator | value |
| authenticated | == | true |
When is_active equals true
Then status is "ADMIN_AUTHENTICATED"
But otherwise status is "ADMIN_DENIED"
@target:sessions @blocking @rule:SessionActivityCheck
Scenario: Session must be active
When is_active equals true
Then status is "SESSION_ACTIVE"
But otherwise status is "SESSION_EXPIRED"Gherkin Tags:
@domain:auth- Domain assignment (feature-level)@target:users- Target model/view name (snake_case plural, matches SQL table)@rule:UserEmailValidation- Rule name@blocking- Rule causes 422 rejection when unsatisfied@view- Target is a view (not a model)
Operator Phrases (natural language):
equals→==,does not equal→!=is greater than→>,is less than→<is at least→>=,is at most→<=
Rule Flow:
- Check
Given ... context requires:metadata (if present) → if fails, returnelsestatus - Check
Whenconditions on model/view fields → returnthenorelsestatus
The system converts .feature files + metadata.yaml into specs.yaml automatically during the build.
make generate-specs
# INPUT: specs.yaml + models.yaml
# OUTPUT: src/main/java-generated/
# ├── dev/appget/specification/generated/ (Specification classes)
# └── dev/appget/specification/context/ (Metadata POJOs)make all
# Runs full pipeline:
# 1. clean (remove old builds)
# 2. features-to-specs (.feature + metadata → specs.yaml)
# 3. generate (proto + specs + registry + openapi)
# 4. test (run 290+ tests)
# 5. build (compile & package)
#
# Result: All 290+ tests passingmake run
# Descriptor-driven evaluation: discovers all models with rules
# Shows which rules passed/failed with their status values
#
# Output:
# --- Rule Engine Evaluation (Descriptor-Based) ---
#
# Model: Users (7 rule(s))
# Rule: UserEmailValidation | Result: VALID_EMAIL
# Rule: UserSuspensionCheck | Result: ACTIVE
# Rule: VerifiedUserRequirement | Result: VERIFIED
# Rule: UsernamePresence | Result: USERNAME_PRESENT
# Rule: UserAccountStatus | Result: GOOD_STANDING
# ...
#
# Model: ModerationActions (2 rule(s))
# Rule: ModerationActionActive | Result: ACTION_ENFORCED
# Rule: ModerationAuthorizationCheck | Result: MODERATION_AUTHORIZEDYou edit: Command: Output:
──────────────────────────────────────────────────────────────
features/*.feature ──→ make features-to-specs ──→ specs.yaml
metadata.yaml ──→ (included in features-to-specs)
schema.sql ──→ make parse-schema ──→ models.yaml
views.sql ──→ (automatic in parse-schema)
(all above) ──→ make generate ──→ protobuf classes + specs
All updated ──→ make test ──→ 290+ tests pass
| I Want To... | Use This | Then Run |
|---|---|---|
| See if everything works | make all |
Nothing, tests run |
| After editing .feature files | make features-to-specs && make generate && make test |
make run to see rules |
| After editing schema.sql | make parse-schema && make generate && make test |
make run to see demo |
| Run only tests | make test |
Repeat edits until pass |
| Generate REST API server | make generate-server |
Check generated-server/ |
| Start Spring Boot server | make run-server |
Hit http://localhost:8080 |
| Clean everything | make clean |
Then make all |
Generated to: src/main/java-generated/dev/appget/*/model/
// Auto-generated via: schema.sql → .proto → protoc
// Protobuf message with Builder pattern
Users user = Users.newBuilder()
.setUsername("alice")
.setEmail("alice@example.com")
.setIsVerified(true)
.setIsSuspended(false)
.build();Generated to: src/main/java-generated/dev/appget/*/view/
// Auto-generated via: views.sql → .proto → protoc
// Protobuf message with Builder pattern
PostDetailView view = PostDetailView.newBuilder()
.setPostId("post-1")
.setPostContent("Hello world")
.setLikeCount(42)
.setAuthorUsername("alice")
.setAuthorVerified(true)
.build();Generated to: src/main/java-generated/dev/appget/specification/generated/
// Auto-generated from: rules: [- name: UserEmailValidation ...]
public class UserEmailValidation implements Specification<Users> {
public boolean isSatisfiedBy(Users target) {
return !target.getEmail().isEmpty();
}
}Generated to: generated-server/dev/appget/server/
generated-server/dev/appget/server/
├── Application.java (Spring Boot main class)
├── controller/ (REST endpoints @RestController)
├── service/ (Business logic @Service)
├── repository/ (In-memory storage @Repository)
├── exception/ (Error handling)
├── dto/ (Request/response objects)
├── config/ (MetadataExtractor for headers)
└── application.yaml (Spring Boot config)
After running make generate-server, you get a complete REST API server with pre-compiled business rule enforcement.
POST /users - Create new user (validates rules)
GET /users - List all users
GET /users/{id} - Get specific user
PUT /users/{id} - Update user (validates rules)
DELETE /users/{id} - Delete user
Same for: /sessions, /posts, /comments, /follows, /moderation-actions, /roles, /api-keys, etc.
# Views are read-only (GET only, no create/update/delete):
GET /views/post-detail - List post detail view entries
GET /views/post-detail/{id} - Get specific post detail view entry
Same for: /views/user-feed, /views/user-stats, /views/comment-detail,
/views/trending-posts, /views/user-oauth, /views/api-key-stats,
/views/user-role, /views/moderation-queue, /views/company-health
make generate-server # Generate Spring Boot code
make run-server # Start server on http://localhost:8080# Create user (will validate UserActivationCheck, UserVerificationStatus, etc.)
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"email": "alice@example.com",
"isVerified": true,
"isActive": true
}'
# Response:
# HTTP 201 Created
# {
# "data": { ... },
# "ruleResults": {
# "outcomes": [
# { "ruleName": "UserActivationCheck", "status": "ACCOUNT_ACTIVE" },
# { "ruleName": "UserVerificationStatus", "status": "VERIFIED_USER" },
# ...
# ],
# "hasFailures": false
# }
# }Rules that check metadata (the requires: block) extract data from HTTP headers.
Headers follow the convention X-{Category}-{Field-Name}:
curl -X POST http://localhost:8080/moderation-actions \
-H "Content-Type: application/json" \
-H "X-Sso-Authenticated: true" \
-H "X-Roles-Is-Admin: true" \
-H "X-Roles-Role-Level: 5" \
-d '{
"reason": "Spam content",
"isActive": true
}'
# MetadataExtractor reads X-{Category}-{Field} headers
# Builds typed context POJOs (SsoContext, RolesContext, UserContext, OauthContext, ApiContext)
# Rules check both metadata AND model fieldsRules can be marked @blocking in .feature files. This affects HTTP responses:
- Blocking rules (
@blockingtag): If unsatisfied, the request returns 422 Unprocessable Entity - Informational rules (default): Always reported in outcomes but never block the request
# Blocking: causes 422 if user is inactive
@target:users @blocking @rule:UserActivationCheck
Scenario: User account must be active
When is_active equals true
Then status is "ACCOUNT_ACTIVE"
But otherwise status is "ACCOUNT_INACTIVE"
# Informational: reported but doesn't block
@target:users @rule:UserVerificationStatus
Scenario: User can be verified badge holder
When is_verified equals true
Then status is "VERIFIED_USER"
But otherwise status is "UNVERIFIED_USER"0. You write business rules in Gherkin .feature files
↓
FeatureToSpecsConverter parses features/*.feature + metadata.yaml
↓
Generates specs.yaml (intermediate representation, git-ignored)
↓
1. You write SQL schema
↓
2. SQLSchemaParser reads schema.sql + views.sql
- Parses table definitions
- Resolves column types
- Maps tables to domains
- Resolves view column types from source tables
↓
3. Generates models.yaml
- Intermediate representation
- All models with fields + types
- All views with fields + types
↓
4. ModelsToProtoConverter reads models.yaml
- Creates .proto files per domain (schema only — no rules in proto)
- protoc compiles .proto → Java protobuf classes + gRPC stubs
- Output in build/generated/
↓
5. SpecificationGenerator reads specs.yaml + models.yaml
- Creates Specification class for each rule
- Creates metadata POJO classes (SsoContext, RolesContext, etc)
↓
6. ProtoOpenAPIGenerator reads .proto files
- Creates OpenAPI 3.0.0 REST specification (full CRUD, security)
↓
7. AppServerGenerator reads models.yaml + specs.yaml
- Creates Spring Boot REST API
- Controllers (HTTP endpoints)
- Services (business logic with rule evaluation)
- Repositories (storage)
- RuleService uses pre-compiled spec classes directly (no runtime YAML)
- MetadataExtractor reads typed headers into context POJOs
↓
8. At runtime
- RuleService evaluates pre-compiled specification classes
- Blocking rules cause 422 rejection; informational rules are reported only
- MetadataExtractor builds typed context POJOs from HTTP headers
- Rules are evaluated when creating/updating entities
Every field maps consistently through the entire pipeline:
SQL Type Java Type OpenAPI Type TypeScript Type
─────────────────────────────────────────────────────────────────
VARCHAR(100) String string string
INT int (Integer*) integer number
DECIMAL(15,2) BigDecimal number number
DATE LocalDate string(date) Date
TIMESTAMP LocalDateTime string(date-time) Date
BOOLEAN boolean boolean boolean
* Integer if nullable in SQL, int if NOT NULL
All non-generated Java classes include Log4j2 logging:
- INFO level (default): File loading, rule evaluation results, important milestones
- DEBUG level: Method entry/exit, detailed execution flow (useful for troubleshooting)
- Configuration:
src/main/resources/log4j2.properties
Adjust logging verbosity:
# Development (see everything)
logger.dev_appget_codegen.level = DEBUG
# Production (see only important events)
logger.dev_appget_codegen.level = INFOEvery time you change schema.sql or specs.yaml, the system regenerates Java code. Tests verify:
- ✓ Models generated correctly from schema
- ✓ Rules evaluated correctly
- ✓ Type mappings are correct
- ✓ Views resolved properly
- ✓ REST endpoint specs are valid
The system includes over 290 unit tests across 16 test suites:
| Suite | Count | What It Tests |
|---|---|---|
| Java Type Registry | 44 | Type mappings: SQL → Java, Proto, OpenAPI; nullability rules |
| Code Gen Utils | 28 | Shared code generation utilities |
| Java Utils | 28 | Java-specific utility functions, name conversions |
| Feature To Specs Converter | 28 | Gherkin parsing, condition extraction, YAML generation, metadata validation |
| Proto-First OpenAPI Generator | 23 | Proto-first OpenAPI, CRUD, security, type mapping |
| Specification Patterns | 21 | All comparison operators, edge cases |
| Schema To Proto Converter | 18 | Proto generation, field mapping, services |
| App Server Generator | 17 | RuleService bridge, MetadataExtractor, blocking logic |
| Conformance | 16 | Proto file output matches expected golden files |
| Rule Engine | 15 | Rule evaluation, metadata, compound logic, type mismatch guard |
| Specification Generator | 13 | Rule generation, metadata, views |
| Descriptor Registry | 9 | Model discovery, lookup, field descriptors |
| gRPC Service Stubs | 7 | Service existence, CRUD method descriptors |
| Test Data Builder | 6 | DynamicMessage generation, default values |
| Compound Specifications | 6 | AND/OR logic combinations |
| Metadata Context | 5 | Authorization metadata storage |
make test # Run all 290+ tests (expect ~2s)
make all # Full pipeline (features → generate → test → build)
make clean && make test # Fresh runAll tests pass → Your code is ready to use. Some fail → Check error messages, fix the issue, re-run.
After running tests, detailed HTML report:
open build/reports/tests/test/index.htmlShows:
- Which test suites passed/failed
- Execution time per test
- Full stack traces for failures
| Task | Time |
|---|---|
make test |
~2s (290+ tests) |
make all |
~5-6s (full pipeline) |
make run |
~1s (demo execution) |
make generate-server |
~1s (server generation) |
| Command | When to Use | What It Does |
|---|---|---|
make all |
After editing ANY file | Full pipeline: clean → generate → test → build |
make test |
To verify your changes | Run all 290+ tests (takes ~2s) |
make run |
To see rules in action | Build + execute demo rule engine |
| Command | When to Use | What It Does |
|---|---|---|
make features-to-specs |
After editing .feature or metadata.yaml | Convert Gherkin → specs.yaml |
make parse-schema |
After editing schema.sql or views.sql | Parse SQL → Generate models.yaml |
make generate-proto |
After editing schema.sql | Protobuf models → build/generated/ |
make generate-specs |
After editing .feature files | Specification classes + metadata |
make generate-openapi |
After editing schema.sql | REST API specification → openapi.yaml (proto-first) |
make generate |
After editing schema, features, or metadata | All generation (features-to-specs + protoc + specs + openapi) |
make generate-server |
When ready for REST API | Spring Boot server → generated-server/ |
| Command | What It Does |
|---|---|
make generate-server |
Generate complete Spring Boot REST API server |
make run-server |
Build + start Spring Boot on port 8080 |
| Command | What It Does |
|---|---|
make clean |
Remove all build artifacts + generated code |
make build |
Compile all code + create JAR |
make help |
Show all available commands |
# 1. Edit your files
vim schema.sql
vim features/auth.feature
# 2. Regenerate + test
make all
# Output: Runs all steps, all 290+ tests should pass
# 3. See rules in action
make run
# Demo shows Users evaluated against all rules
# 4. When ready for REST API
make generate-server
make run-server
# Server runs on http://localhost:8080
# 5. Test an endpoint
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"username":"alice","email":"alice@example.com","isVerified":true,"isSuspended":false}'Your database schema defines everything. No manual type definitions.
-- auth domain
CREATE TABLE users (
id VARCHAR(50) NOT NULL, -- String id
username VARCHAR(100) NOT NULL, -- String username
email VARCHAR(255) NOT NULL, -- String email
is_verified BOOLEAN NOT NULL, -- boolean isVerified
is_active BOOLEAN NOT NULL -- boolean isActive
);✓ Multi-dialect SQL support (MySQL, PostgreSQL, SQLite, Oracle, MSSQL) ✓ NOT NULL constraints honored (primitives stay primitive, nullable fields wrapped) ✓ snake_case naming throughout (schema, metadata, features)
Define read-optimized views for reporting. System auto-resolves column types.
-- social domain
CREATE VIEW post_detail_view AS
SELECT
p.id AS post_id,
p.like_count AS like_count,
u.username AS author_username,
u.is_verified AS author_verified
FROM posts p
JOIN users u ON p.author_id = u.id;Generated as Java class:
// PostDetailView (protobuf MessageOrBuilder)
public int getLikeCount() // ✓ Type resolved from posts.like_count
public String getAuthorUsername() // ✓ Type resolved from users.username
public boolean getAuthorVerified()// ✓ Type resolved from users.is_verified✓ View classes in view/ subpackage
✓ Views can be targeted by rules (just like models)
Write rules in YAML. System enforces them automatically.
- name: HighEngagementPost
conditions:
operator: AND # All must be true
clauses:
- field: like_count >= 1000
- field: is_public == true
then:
status: "HIGH_ENGAGEMENT"✓ Simple conditions (single field checks) ✓ Compound conditions (AND/OR multiple fields) ✓ Rules evaluated automatically on create/update ✓ Rule failures return specific status values
Rules can require caller context (roles, SSO, etc.) before evaluating model fields.
Metadata categories are cross-cutting concerns — they represent "who is making this request?" context, not database tables. They have no corresponding tables in schema.sql.
In a .feature file:
@target:users @blocking @rule:AdminAuthenticationRequired
Scenario: User with admin role can manage system
Given roles context requires:
| field | operator | value |
| role_level | >= | 3 |
And sso context requires:
| field | operator | value |
| authenticated | == | true |
When is_active equals true
Then status is "ADMIN_AUTHENTICATED"
But otherwise status is "ADMIN_DENIED"What happens at runtime:
MetadataExtractorreads HTTP headers (X-Roles-Role-Level,X-Sso-Authenticated) into typed POJOs- Rule checks
roles.role_level >= 3andsso.authenticated == truefirst - Only if metadata passes, evaluates
users.is_active == trueon the model - Returns
"ADMIN_AUTHENTICATED"or"ADMIN_DENIED"
HTTP headers follow the convention X-{Category}-{Field-Name}:
curl -X POST http://localhost:8080/users \
-H "X-Sso-Authenticated: true" \
-H "X-Roles-Role-Level: 5" \
-H "X-Roles-Is-Admin: true" \
-d '{"username":"alice","email":"alice@example.com","isActive":true}'metadata.yaml is a curated registry of 14 built-in categories, each with an enabled: true/false toggle. Only enabled categories flow through the pipeline. Categories are independent of schema.sql — they represent request-scoped context.
Current categories (3 pre-enabled by default, 2 additionally enabled for this project):
| Category | Enabled | Purpose | Fields |
|---|---|---|---|
| sso | yes (default) | Single sign-on session state | authenticated, session_id, provider |
| user | yes (default) | Authenticated user identity | user_id, email, username |
| roles | yes (default) | Role-based access control | role_name, role_level, is_admin |
| oauth | yes (project) | OAuth 2.0 token context | access_token, scope, expires_in, provider |
| api | yes (project) | API key authentication | api_key, rate_limit_tier, is_active |
| jwt | no | JWT token claims | subject, issuer, audience, expires_at |
| mfa | no | Multi-factor auth state | verified, method |
| permissions | no | Fine-grained permissions | permission_name, resource_type, can_read, can_write |
| tenant | no | Multi-tenant isolation | tenant_id, tenant_name, plan, is_active |
| billing | no | Billing/subscription | customer_id, plan, is_active, billing_cycle |
| payments | no | Payment processing | payment_method_id, provider, currency, is_verified |
| invoice | no | Invoice records | invoice_id, status, amount, is_paid |
| audit | no | Request audit trail | request_id, source_ip, user_agent |
| geo | no | Geolocation context | country, region, timezone |
To enable a built-in category: Set enabled: true in metadata.yaml.
To add a custom category: Add a new entry at the bottom of metadata.yaml:
bitcoin:
enabled: true
description: "Bitcoin node and wallet context"
fields:
- name: address
type: String
- name: wallet
type: StringBuild-time validation: The pipeline validates all Given <category> context requires: references:
- Unknown category → build error
- Disabled category → build error with guidance to enable
- Unknown field in enabled category → build error
How metadata flows through the pipeline:
metadata.yaml features/*.feature
(14 categories, (Given roles context requires:)
5 enabled) (And sso context requires:)
│ │
└──────────┬───────────────────────┘
▼
FeatureToSpecsConverter
(filters enabled-only, validates references)
│
▼
specs.yaml
(metadata section: 5 categories)
(rules with requires: blocks)
│
┌───────┴───────────┐
▼ ▼
SpecificationGenerator AppServerGenerator
│ │
▼ ▼
5 Context POJOs MetadataExtractor
(SsoContext.java, (reads X-{Category}-{Field}
RolesContext.java, HTTP headers into POJOs)
UserContext.java, │
OauthContext.java, ▼
ApiContext.java) RuleService
│ (evaluates specs with
▼ metadata context)
33 Spec classes
(AdminAuthenticationRequired
checks roles + sso before
evaluating users.is_active)
Organize models by business domain. System auto-creates packages.
schema.sql (with -- <domain> domain comments):
admin: roles, user_roles, moderation_actions, company_settings
auth: users, oauth_providers, oauth_tokens, api_keys, sessions
social: posts, comments, likes, follows, feeds
✓ Logical separation of concerns ✓ Prevents naming conflicts ✓ Each domain gets own package namespace
Generate complete Spring Boot REST server from your schema.
make generate-server
# Generates:
# Controllers (REST endpoints)
# Services (business logic + rule validation)
# Repositories (in-memory storage)
# DTOs (request/response objects)
# Exception handling✓ CRUD endpoints for all models ✓ Rules validated on create/update ✓ Metadata extracted from HTTP headers ✓ Proper HTTP status codes (201 Created, 422 Unprocessable Entity, etc)
Every type maps consistently from SQL → Java → REST → TypeScript.
SQL Java OpenAPI TypeScript
────────────────────────────────────────────────────────────────
VARCHAR(100) String string string
INT NOT NULL int integer number
DECIMAL(15,2) BigDecimal number number
DATE LocalDate string(date) Date
✓ No type mismatches between layers ✓ TypeScript client can be auto-generated from OpenAPI spec ✓ Full end-to-end type safety
All 290+ tests pass automatically:
✓ Gherkin .feature file parsing and specs.yaml generation
✓ Proto generation correctness (schema → .proto → Java)
✓ Descriptor-based rule evaluation
✓ Type mapping validation
✓ View resolution
✓ REST endpoint specs (proto-first OpenAPI)
✓ gRPC service stubs
✓ Descriptor registry and test data builder
Run: make test to verify your changes at any time.
appget.dev/java/
├── features/
│ ├── admin.feature 🖊️ Admin domain business rules (Gherkin)
│ ├── auth.feature 🖊️ Auth domain business rules (Gherkin)
│ └── social.feature 🖊️ Social domain business rules (Gherkin)
├── metadata.yaml 🖊️ Curated metadata registry (14 categories, toggle model)
├── schema.sql 🖊️ Your database schema (tables)
├── views.sql 🖊️ Your read models (views)
├── build.gradle 🖊️ Java build configuration
├── Makefile 🖊️ Build commands
└── src/main/java/dev/appget/
├── codegen/ 📝 Code generators (do not modify)
├── model/
│ └── Rule.java 📝 Rule engine (do not modify)
├── specification/ 📝 Rule evaluation logic (do not modify)
├── util/ 📝 DescriptorRegistry, DefaultDataBuilder
└── RuleEngine.java 📝 Descriptor-driven demo app (do not modify)
appget.dev/java/
├── specs.yaml ✨ Auto-generated from features/*.feature + metadata.yaml
├── models.yaml ✨ Auto-generated from schema.sql
├── openapi.yaml ✨ Auto-generated REST spec
├── src/main/java-generated/✨ All generated Java models, specs
│ └── dev/appget/
│ ├── auth/model/ (Users, Sessions, OauthProviders, OauthTokens, ApiKeys)
│ ├── social/model/ (Posts, Comments, Likes, Follows, Feeds)
│ ├── social/view/ (PostDetailView, UserFeedView, TrendingPostsView, etc)
│ ├── admin/model/ (Roles, UserRoles, ModerationActions, CompanySettings)
│ └── specification/
│ ├── generated/ (Specification classes)
│ └── context/ (Metadata POJOs)
└── generated-server/ ✨ Spring Boot REST API
└── dev/appget/server/
├── controller/ (REST endpoints)
├── service/ (Business logic)
├── repository/ (Storage)
├── dto/ (Request/response)
└── exception/ (Error handling)
src/test/java/dev/appget/
├── codegen/
│ ├── AppServerGeneratorTest.java (17 tests)
│ ├── CodeGenUtilsTest.java (28 tests)
│ ├── FeatureToSpecsConverterTest.java (24 tests)
│ ├── JavaTypeRegistryTest.java (44 tests)
│ ├── JavaUtilsTest.java (28 tests)
│ ├── ModelsToProtoConverterTest.java (18 tests)
│ ├── ProtoOpenAPIGeneratorTest.java (23 tests)
│ └── SpecificationGeneratorTest.java (13 tests)
├── conformance/
│ └── ConformanceTest.java (16 tests)
├── model/
│ └── RuleTest.java (15 tests)
├── service/
│ └── GrpcServiceTest.java (7 tests)
├── specification/
│ ├── CompoundSpecificationTest.java (6 tests)
│ ├── MetadataContextTest.java (5 tests)
│ └── SpecificationTest.java (21 tests)
└── util/
├── DescriptorRegistryTest.java (9 tests)
└── DefaultDataBuilderTest.java (6 tests)
Define rules in .feature files using natural language phrases (is greater than, equals, etc.). The generated specs.yaml uses these operators in conditions:
conditions:
- field: age
operator: ">" # Greater than
value: 18
- field: salary
operator: ">=" # Greater than or equal
value: 50000
- field: years
operator: "<" # Less than
value: 5
- field: bonus
operator: "<=" # Less than or equal
value: 10000
- field: level
operator: "==" # Equals
value: 3
- field: status
operator: "!=" # Not equals
value: "INACTIVE"conditions:
- field: name
operator: "==" # Equals
value: "Manager"
- field: role
operator: "!=" # Not equals
value: "Guest"conditions:
- field: active
operator: "==" # Must be true
value: true
- field: verified
operator: "!=" # Must be false
value: falseProblem: System can't find your database schema
Solution: Create schema.sql at project root with table definitions
Problem: Some of the 290+ tests failed Solution:
# 1. Check the error message
# 2. Look at what file caused it (schema.sql, specs.yaml, etc)
# 3. Fix that file
# 4. Run make all againProblem: Models generated incorrectly Solution:
# Clean and regenerate everything
make clean
make allProblem: Java model not found Solution:
# Make sure table exists in schema.sql
# Make sure you ran: make parse-schema && make generate
make allProblem: Business rule not running Solution:
- Check
features/*.feature- does scenario exist with correct tags? - Check rule
@targettag - does model exist? - Check field names - match your model exactly?
- Run
make features-to-specs && make generate-specsto regenerate - Run
make runto see demo
Problem: View field has wrong Java type Solution:
- Check source table in
schema.sql- is column type correct? - Check view SQL - is alias correct?
- Make sure source table defined BEFORE view in SQL file
- Run
make parse-schemato re-parse
Problem: make run-server fails
Solution:
# Make sure server is generated
make generate-server
# Check generated code exists
ls -la generated-server/dev/appget/server/
# If missing, regenerate everything
make clean && make generate-serverProblem: Another process using the port Solution:
# Kill the other process
lsof -i :8080
kill -9 <PID>
# Or use different port
# Edit generated-server/src/main/resources/application.yaml:
# server:
# port: 8081- Java: 25+ (OpenJDK 25 or newer)
- Gradle: 9.3.1+ (automatic wrapper download)
- Gherkin: 38.0.0 (Cucumber Gherkin parser for
.featurefiles) - Protobuf: 3.25.3 (protoc compiler + Java runtime)
- gRPC-Java: 1.62.2 (gRPC service stubs)
- Lombok: 1.18.38+ (metadata POJOs, Java 25 compatible)
- JSQLParser: 5.3 (multi-dialect SQL support)
- SnakeYAML: 2.2 (YAML parsing)
- Log4j2: 2.23.1 (logging)
- JUnit 5: 5.11.3 (testing)
# Check Java version
java -version
# Output should show 25 or higher
# Check Gradle
./gradlew --version
# Should work (auto-installs if needed)
# Run a quick test
make test
# Should show 290+ tests passingThe domain-architect agent (../.claude/agents/domain-architect.md) translates high-level business descriptions into appget source files (schema.sql, views.sql, features/*.feature, metadata.yaml).
Example prompt:
Create a social media application with MVP functionality for regular users, company admins, GitHub OAuth2, and API keys for 3rd party developers. Define SQL views for cross-table reads (JOINs), aggregated analytics (GROUP BY), and transactional writes spanning multiple tables. The REST API must be fully featured — not just basic CRUD, but complete endpoints for every complex operation.
After the agent runs, execute make all to generate the full backend from the produced source files.
For more details on implementation and architecture:
- CLAUDE.md - Technical implementation guide for Claude Code
- PIPELINE.md - Detailed pipeline architecture
- ai.chat - Original design discussion and concepts
Last Updated: 2026-02-27 Status: Production Ready Test Coverage: 290+ tests, 100% passing (16 suites) Pipeline: Gherkin → specs.yaml → Schema → Proto → Protoc → Specs → REST API → gRPC → Fully Typed Metadata: 14 built-in categories with toggle model (5 enabled for this project) Java Version: 25+ required