Skip to main content

Archetype

Archetype composition enables rendering other archetypes from within an archetype, creating powerful modular code generation capabilities. This sophisticated feature allows complex archetypes to be built from smaller, focused components while maintaining proper isolation and configuration inheritance.

Overview

Archetype composition works by:

  1. Component Registration: Defining component archetypes in the parent's archetype.yaml
  2. Script Rendering: Using the Archetype() function in scripts to render components
  3. Context Passing: Sharing variables, settings, and switches between parent and child archetypes
  4. Destination Control: Specifying where component content is generated
Core Concept

Every archetype that renders other archetypes must register those components in its archetype.yaml manifest. This ensures security and predictable behavior.

No Automatic Inheritance

Component archetypes do not automatically inherit switches, answers, or settings from the parent archetype. All data must be explicitly passed down through the render method parameters. This gives you full control over what information component archetypes can access.

Component Registration

Defining Components in archetype.yaml

Components must be declared in the components section of the parent archetype's manifest:

# archetype.yaml
name: "enterprise-java-service"
description: "Complete Java service with common components"

components:
# Local component archetypes
rest-api: "components/rest-api"
database-layer: "components/database"

# GitHub-hosted components
security-config: "git@github.com:company/security-archetype.git"
monitoring: "https://github.com/company/monitoring-archetype.git"

# Version-specific components
ci-pipeline: "git@github.com:company/ci-archetype.git#v2.1.0"

Component Path Types

Path TypeExampleDescription
Relative"components/api"Local directory relative to parent archetype
Absolute"/path/to/archetype"Absolute filesystem path
Git SSH"git@github.com:org/repo.git"SSH-based Git repository
Git HTTPS"https://github.com/org/repo.git"HTTPS-based Git repository
Git Tagged"git@github.com:org/repo.git#v1.0.0"Specific Git tag or branch
Security

Component paths are validated to prevent directory traversal attacks. All paths must resolve to safe, accessible locations.

The Archetype() Function

Basic Usage

The Archetype() function creates a reference to a registered component:

// Basic component rendering
Archetype("rest-api").render(#{});

// Rendering with context variables
Archetype("database-layer").render(#{
database_type: "postgresql",
schema_name: "app_schema"
});

Function Signature

Archetype(component_key)

Parameters:

  • component_key - String identifier matching a key in the components section

Returns: Archetype object with render methods

Error Conditions:

  • Component key not found in manifest: "Archetypes must be registered in archetype.yaml, and 'unknown-key' archetype has not been listed there"
  • Invalid component path or inaccessible archetype

Render Methods

The Archetype object provides flexible rendering options:

Call SignatureDescription
render(context)Basic rendering with default settings and destination
render(context, settings)Rendering with custom configuration options
render(destination, context)Rendering to specific destination (string or Path)
render(destination, context, settings)Complete control with destination and settings

render(context)

Basic rendering with default settings and destination.

// Simple component rendering
Archetype("api-layer").render(#{
service_name: "user-service",
port: 8080
});

render(context, settings)

Rendering with custom configuration options.

// Rendering with switches and settings
Archetype("security-layer").render(#{
auth_provider: "oauth2",
enable_rbac: true
}, #{
switches: ["security", "audit-logging"],
use_defaults: ["auth_method", "encryption_key"]
});

render(destination, context)

Rendering to a specific destination directory.

// Render to custom destination
Archetype("frontend-app").render("./web-client", #{
app_name: "user-dashboard",
api_base_url: "https://api.example.com"
});

// Using Path object for destination
let dest_path = Path("./services/auth");
Archetype("auth-service").render(dest_path, #{
database_url: "postgresql://localhost/auth_db"
});

render(destination, context, settings)

Complete control with destination, context, and settings.

// Full configuration rendering
Archetype("microservice-template").render(
"./services/payment",
#{
service_name: "payment-service",
database_type: "postgresql",
message_queue: "rabbitmq"
},
#{
switches: ["database", "messaging", "monitoring"],
use_defaults_all: false
}
);

Practical Examples

Enterprise Java Service Composition

# archetype.yaml
name: "enterprise-java-service"
description: "Complete Java microservice with enterprise patterns"

components:
spring-boot-base: "components/spring-boot"
rest-controllers: "components/rest-api"
jpa-entities: "components/database"
security-config: "components/security"
docker-setup: "components/containerization"
ci-pipeline: "git@github.com:company/java-ci.git"
// archetype.rhai - Service composition script

// Core Spring Boot application
Archetype("spring-boot-base").render(#{
group_id: context.group_id,
artifact_id: context.service_name,
java_version: context.java_version,
spring_boot_version: "3.2.0"
});

// REST API layer
Archetype("rest-controllers").render("./src/main/java", #{
package_name: context.package_name,
service_name: context.service_name,
api_version: "v1"
});

// Database layer with JPA
if switch_enabled("database") {
Archetype("jpa-entities").render("./src/main/java", #{
package_name: context.package_name + ".model",
database_type: context.database_type,
schema_name: context.service_name
});
}

// Security configuration
if switch_enabled("security") {
Archetype("security-config").render(#{
auth_method: context.auth_method,
enable_rbac: true,
jwt_secret_key: context.jwt_secret
});
}

// Docker and deployment
Archetype("docker-setup").render(#{
base_image: "openjdk:21-jre-slim",
service_port: context.server_port,
health_check_path: "/actuator/health"
});

// CI/CD pipeline
if switch_enabled("ci-cd") {
Archetype("ci-pipeline").render("./ci", #{
service_name: context.service_name,
docker_registry: context.docker_registry,
deployment_env: ["dev", "staging", "prod"]
}, #{
switches: ["docker", "kubernetes", "sonarqube"]
});
}

Multi-Language Project Structure

# archetype.yaml
name: "full-stack-application"
description: "Complete web application with frontend and backend"

components:
backend-api: "components/node-api"
frontend-app: "components/react-app"
database-schema: "components/postgresql"
deployment-config: "git@github.com:company/k8s-deploy.git"
// Multi-component application setup

// Backend API service
Archetype("backend-api").render("./backend", #{
app_name: context.app_name,
database_url: context.database_url,
cors_origins: [context.frontend_url]
}, #{
switches: ["typescript", "graphql", "testing"]
});

// Frontend application
Archetype("frontend-app").render("./frontend", #{
app_name: context.app_name + "-client",
api_base_url: context.api_url,
theme: context.ui_theme
}, #{
switches: ["typescript", "pwa", "testing"],
use_defaults: true
});

// Database schema and migrations
Archetype("database-schema").render("./database", #{
schema_name: context.app_name,
initial_data: context.seed_data
});

// Kubernetes deployment manifests
if switch_enabled("kubernetes") {
Archetype("deployment-config").render("./k8s", #{
app_name: context.app_name,
namespace: context.environment,
replicas: context.replica_count,
ingress_host: context.domain_name
});
}

Conditional Component Rendering

// Conditional archetype composition based on project type

let project_type = context.project_type;

// Base application structure (always rendered)
Archetype("base-structure").render(#{
project_name: context.project_name,
author: context.author
});

// Conditional component rendering
switch project_type {
"web-api" => {
Archetype("rest-api").render("./src", context);
Archetype("database").render("./src", context);

if context.enable_auth {
Archetype("authentication").render("./src", #{
auth_provider: context.auth_provider
});
}
},

"cli-tool" => {
Archetype("command-parser").render("./src", context);
Archetype("config-management").render("./src", context);
},

"library" => {
Archetype("lib-structure").render("./src", context);

if switch_enabled("examples") {
Archetype("example-code").render("./examples", context);
}
}
}

// Common development tools (conditional)
if switch_enabled("testing") {
Archetype("test-framework").render("./tests", #{
test_type: context.test_framework,
coverage_threshold: 80
});
}

if switch_enabled("ci-cd") {
Archetype("github-actions").render("./.github", #{
workflow_name: context.project_name + "-ci",
node_version: context.node_version
});
}

Settings Reference

Archetype rendering supports the following settings in the settings map:

SettingTypeDescription
switchesArray of stringsFeature switches to enable in the child archetype
use_defaultsArray of stringsSpecific prompt keys to use default values for
use_defaults_allBooleanUse default values for all prompts without asking

Example settings map:

let settings = #{
switches: ["feature1", "feature2"],
use_defaults: ["prompt_name1", "prompt_name2"],
use_defaults_all: false
};

Settings and Configuration

File Handling Policies

Archetype rendering does not support file overwrite policies (if_exists). Individual files rendered by child archetypes will follow their default behavior. For fine-grained file handling control, use Directory rendering instead.

Switch Passing

Switches must be explicitly passed down to child archetypes. They are not automatically inherited from the parent or command line:

// Command line switches: --switch database --switch api
// These are NOT automatically available to components

// Explicitly pass current switches to child archetype
Archetype("backend-service").render(context, #{
switches: SWITCHES // Pass current switches from parent
});

// Extend switches for specific components
Archetype("enhanced-service").render(context, #{
switches: SWITCHES + ["authentication", "logging"]
});

// Use only specific switches (ignore parent switches)
Archetype("minimal-component").render(context, #{
switches: ["essential-only"]
});

// Component gets no switches (even if parent has them)
Archetype("isolated-component").render(context);

Default Handling

Control how child archetypes handle default values:

// Use all defaults in child archetype
Archetype("quick-setup").render(context, #{
use_defaults_all: true
});

// Use defaults for specific prompts
Archetype("guided-setup").render(context, #{
use_defaults: ["database_type", "auth_method"]
});

// No defaults, prompt for everything
Archetype("custom-setup").render(context, #{
use_defaults_all: false
});

Switch and Default Configuration

Control archetype behavior with switches and default handling:

// Force all prompts to use default values
Archetype("quick-setup").render(context, #{
use_defaults_all: true,
switches: ["production", "optimized"]
});

// Use defaults for specific prompts only
Archetype("guided-setup").render(context, #{
use_defaults: ["database_type", "auth_method"],
switches: ["security", "monitoring"]
});

// No defaults, prompt for everything
Archetype("interactive-setup").render(context, #{
use_defaults_all: false,
switches: ["development", "debug"]
});

Advanced Patterns

Dynamic Component Selection

// Select components based on runtime conditions
let database_component = switch context.database_type {
"postgresql" => "postgresql-setup",
"mysql" => "mysql-setup",
"mongodb" => "mongodb-setup",
_ => "sqlite-setup"
};

Archetype(database_component).render("./database", context);

// Platform-specific components
let platform_components = #{
"aws": ["aws-lambda", "rds-config", "s3-storage"],
"azure": ["azure-functions", "cosmos-db", "blob-storage"],
"gcp": ["cloud-functions", "firestore", "cloud-storage"]
};

for component in platform_components[context.cloud_platform] {
Archetype(component).render("./cloud", context);
}

Iterative Component Rendering

// Render multiple instances of the same component
for service in context.microservices {
let service_context = context.clone();
service_context.service_name = service.name;
service_context.service_port = service.port;

Archetype("microservice-template").render(
`./services/${service.name}`,
service_context
);
}

// Generate multiple environments
let environments = ["development", "staging", "production"];
for env in environments {
Archetype("environment-config").render(`./config/${env}`, #{
environment: env,
debug_enabled: env == "development",
replicas: if env == "production" { 3 } else { 1 }
});
}

Hierarchical Composition

// Multi-level archetype composition
// Level 1: Application structure
Archetype("app-foundation").render(context);

// Level 2: Domain modules (each may compose further)
for domain in context.domains {
Archetype("domain-module").render(`./src/${domain}`, #{
domain_name: domain,
entities: context.domain_entities[domain]
});
}

// Level 3: Cross-cutting concerns
Archetype("logging-setup").render("./src/infrastructure", context);
Archetype("monitoring-setup").render("./src/infrastructure", context);

Error Handling and Validation

Component Validation

// Validate required components before rendering
let required_components = ["base-app", "database"];
for component in required_components {
if !component_exists(component) {
throw `Required component '${component}' not found in archetype.yaml`;
}
}

// Conditional rendering with error handling
try {
Archetype("external-service").render(context);
} catch (error) {
print(`Warning: External service setup failed: ${error}`);
print("Continuing with local development setup...");

Archetype("local-development").render(context);
}

Context Validation

// Validate context before component rendering
let validate_context = |ctx| {
let required_fields = ["project_name", "author", "version"];
for field in required_fields {
if !(field in ctx) {
throw `Missing required field: ${field}`;
}
}

if ctx.project_name.len() < 3 {
throw "Project name must be at least 3 characters";
}
};

validate_context(context);

// Safe rendering with validated context
Archetype("validated-setup").render(context);

Integration with Manifest Components

Cross-Reference

Archetype components must be properly registered in the manifest. For detailed information about archetype configuration, see Working with Archetypes.

Component Manifest Structure

Components in archetype.yaml use a simple key-value mapping format:

# archetype.yaml
components:
# Local component references
web-server: "components/nginx"
database: "components/postgresql"

# Git repository components
security-module: "git@github.com:company/security-archetype.git"
ci-pipeline: "https://github.com/company/ci-archetype.git"

# Git components with specific tags/branches
monitoring: "git@github.com:company/monitoring.git#v2.1.0"
Simple Format

Components are defined as simple key-value pairs where the key is the component name used in scripts and the value is the path or URI to the component archetype. Metadata like description or version is not supported in the components section.

Inheritance and Isolation

Explicit Data Flow

Archetype composition uses explicit data flow rather than automatic inheritance:

// Command line: archetect render --switch production --switch database myapp

// In parent archetype script:
// SWITCHES = ["production", "database"] // Available from command line
// context = { app_name: "myapp", version: "1.0" } // From prompts

// Component sees NOTHING unless explicitly passed
Archetype("isolated").render(#{}); // No switches, no context

// Component sees only what you give it
Archetype("partial").render(#{
name: context.app_name // Only app_name, not version
}, #{
switches: ["database"] // Only database switch, not production
});

// Component sees everything you choose to pass
Archetype("full-access").render(context, #{
switches: SWITCHES
});

Benefits of Explicit Control

  • Security: Components can't access sensitive data unless explicitly provided
  • Isolation: Components remain independent and testable
  • Flexibility: Different components can receive different data subsets
  • Clarity: Data flow is explicit and visible in the script

Best Practices

Component Design

Modular Design

Design components to be focused, reusable, and loosely coupled. Each component should handle a specific concern or technology.

// Good: Focused, single-responsibility components
Archetype("database-layer").render("./src/data", context);
Archetype("api-controllers").render("./src/api", context);
Archetype("business-logic").render("./src/business", context);

// Avoid: Monolithic components that do everything
// Archetype("entire-application").render(context); // Too broad

Context Management

Component archetypes only receive the context you explicitly pass to them:

// Parent archetype has full context
// context = { project_name: "myapp", database_type: "postgres", api_version: "v1" }

// Create specific context for each component
let database_context = #{
database_type: context.database_type, // Explicitly select what to pass
schema_name: context.project_name,
migration_strategy: context.migration_strategy
};

let api_context = #{
service_name: context.project_name, // Control what each component sees
version: context.api_version,
authentication: context.auth_enabled
};

// Each component only gets the context you provide
Archetype("database-setup").render("./database", database_context);
Archetype("api-layer").render("./api", api_context);

// This component gets NO context variables
Archetype("static-component").render("./static", #{});

Performance Optimization

// Render components in parallel when possible
// (Archetect automatically handles concurrency safety)

let components = [
["frontend-app", "./frontend"],
["backend-api", "./backend"],
["database-schema", "./database"]
];

for [component, destination] in components {
Archetype(component).render(destination, context);
}

Testing Component Composition

// Include testing components conditionally
if switch_enabled("testing") {
// Unit tests for each layer
Archetype("unit-tests").render("./tests/unit", context);

// Integration tests
Archetype("integration-tests").render("./tests/integration", context);

// End-to-end tests
if switch_enabled("e2e-tests") {
Archetype("e2e-tests").render("./tests/e2e", context);
}
}

Common Patterns

PatternUse CaseKey Features
Service MeshMultiple microservices with shared infrastructureService iteration, shared context, infrastructure components
Layered ArchitectureClean architecture with separated concernsLayer-specific components, structured organization
Conditional CompositionFeature-based project generationSwitch-based rendering, dynamic component selection
Environment-SpecificMulti-environment deploymentsConfiguration per environment, deployment variations

Service Mesh Architecture

// Service mesh with multiple services
let services = context.services;
let shared_context = #{
namespace: context.namespace,
service_mesh: "istio",
monitoring_enabled: true
};

// Generate each service
for service in services {
let service_context = shared_context.clone();
service_context.service_name = service.name;
service_context.service_type = service.type;

Archetype("microservice").render(
`./services/${service.name}`,
service_context
);
}

// Shared infrastructure
Archetype("service-mesh-config").render("./infrastructure", shared_context);
Archetype("monitoring-stack").render("./monitoring", shared_context);

Layered Architecture

// Generate layered application architecture
let layers = [
["presentation", "./src/presentation"],
["application", "./src/application"],
["domain", "./src/domain"],
["infrastructure", "./src/infrastructure"]
];

for [layer, path] in layers {
Archetype(`${layer}-layer`).render(path, #{
layer_name: layer,
project_name: context.project_name
});
}

Archetype composition provides unprecedented flexibility in code generation, enabling the creation of sophisticated, modular archetypes that can adapt to diverse project requirements while maintaining consistency and best practices.

Next Steps