Command Line Tools
Service Architecture Guide
Service Architecture Guide
Section titled “Service Architecture Guide”Understanding the Wheels CLI service layer architecture.
Overview
Section titled “Overview”The Wheels CLI uses a service-oriented architecture that separates concerns and provides reusable functionality across commands. This architecture makes the CLI more maintainable, testable, and extensible.
Architecture Diagram
Section titled “Architecture Diagram”┌─────────────────┐│ Commands │ ← User interacts with├─────────────────┤│ Services │ ← Business logic├─────────────────┤│ Models │ ← Data and utilities├─────────────────┤│ Templates │ ← Code generation└─────────────────┘Core Components
Section titled “Core Components”1. Commands (/commands/wheels/)
Section titled “1. Commands (/commands/wheels/)”Commands are the user-facing interface:
component extends="wheels-cli.models.BaseCommand" {
property name="codeGenerationService" inject="CodeGenerationService@wheels-cli"; property name="migrationService" inject="MigrationService@wheels-cli";
function run( required string name, boolean migration = true, string properties = "", boolean force = false ) { // Delegate to services var result = codeGenerationService.generateModel(argumentCollection=arguments);
if (arguments.migration) { migrationService.createTableMigration(arguments.name, arguments.properties); }
print.greenLine("✓ Model generated successfully"); }}2. Services (/models/)
Section titled “2. Services (/models/)”Services contain reusable business logic:
component accessors="true" singleton {
property name="templateService" inject="TemplateService@wheels-cli"; property name="fileService" inject="FileService@wheels-cli";
function generateModel( required string name, string properties = "", struct associations = {}, boolean force = false ) { var modelName = helpers.capitalize(arguments.name); var template = templateService.getTemplate("model"); var content = templateService.populateTemplate(template, { modelName: modelName, properties: parseProperties(arguments.properties), associations: arguments.associations });
return fileService.writeFile( path = "/models/#modelName#.cfc", content = content, force = arguments.force ); }}3. Base Classes
Section titled “3. Base Classes”BaseCommand (/models/BaseCommand.cfc)
Section titled “BaseCommand (/models/BaseCommand.cfc)”All commands extend from BaseCommand:
component extends="commandbox.system.BaseCommand" {
property name="print" inject="print"; property name="fileSystemUtil" inject="FileSystem";
function init() { // Common initialization return this; }
// Common helper methods function ensureDirectoryExists(required string path) { if (!directoryExists(arguments.path)) { directoryCreate(arguments.path, true); } }
function resolvePath(required string path) { return fileSystemUtil.resolvePath(arguments.path); }}Service Catalog
Section titled “Service Catalog”Core Services
Section titled “Core Services”TemplateService
Section titled “TemplateService”Manages code generation templates with override system:
- Template Loading: Searches app snippets first, then CLI templates
- Variable Substitution: Replaces placeholders with actual values
- Custom Template Support: Apps can override any CLI template
- Path Resolution:
app/snippets/overrides/cli/templates/ - Dynamic Content: Generates form fields, validations, relationships
Key features:
- Template hierarchy allows project customization
- Preserves markers for future CLI additions
- Supports conditional logic in templates
- Handles both simple and complex placeholders
See Template System Guide for detailed documentation.
MigrationService
Section titled “MigrationService”Handles database migrations:
- Generate migration files
- Track migration status
- Execute migrations
TestService
Section titled “TestService”Testing functionality:
- Run TestBox tests
- Generate coverage reports
- Watch mode support
CodeGenerationService
Section titled “CodeGenerationService”Centralized code generation:
- Generate models, controllers, views
- Handle associations
- Manage validations
Feature Services
Section titled “Feature Services”AnalysisService
Section titled “AnalysisService”Code analysis tools:
- Complexity analysis
- Code style checking
- Dependency analysis
SecurityService
Section titled “SecurityService”Security scanning:
- SQL injection detection
- XSS vulnerability scanning
- Hardcoded credential detection
OptimizationService
Section titled “OptimizationService”Performance optimization:
- Cache analysis
- Query optimization
- Asset optimization
PluginService
Section titled “PluginService”Plugin management:
- Install/remove plugins
- Version management
- Dependency resolution
EnvironmentService
Section titled “EnvironmentService”Environment management:
- Environment switching
- Configuration management
- Docker/Vagrant support
Dependency Injection
Section titled “Dependency Injection”Services use WireBox for dependency injection:
// In ModuleConfig.cfcfunction configure() { // Service mappings binder.map("TemplateService@wheels-cli") .to("wheels.cli.models.TemplateService") .asSingleton();
binder.map("MigrationService@wheels-cli") .to("wheels.cli.models.MigrationService") .asSingleton();}Creating a New Service
Section titled “Creating a New Service”1. Create Service Component
Section titled “1. Create Service Component”component accessors="true" singleton {
// Inject dependencies property name="fileService" inject="FileService@wheels-cli"; property name="print" inject="print";
function init() { return this; }
function doSomething(required string input) { // Service logic here return processInput(arguments.input); }
private function processInput(required string input) { // Private helper methods return arguments.input.trim(); }}2. Register Service
Section titled “2. Register Service”In /ModuleConfig.cfc:
binder.map("MyNewService@wheels-cli") .to("wheels.cli.models.MyNewService") .asSingleton();3. Use in Commands
Section titled “3. Use in Commands”component extends="wheels-cli.models.BaseCommand" {
property name="myNewService" inject="MyNewService@wheels-cli";
function run(required string input) { var result = myNewService.doSomething(arguments.input); print.line(result); }}Service Patterns
Section titled “Service Patterns”1. Singleton Pattern
Section titled “1. Singleton Pattern”Most services are singletons:
component singleton { // Shared instance across commands}2. Factory Pattern
Section titled “2. Factory Pattern”For creating multiple instances:
component { function createGenerator(required string type) { switch(arguments.type) { case "model": return new ModelGenerator(); case "controller": return new ControllerGenerator(); } }}3. Strategy Pattern
Section titled “3. Strategy Pattern”For different implementations:
component { function setStrategy(required component strategy) { variables.strategy = arguments.strategy; }
function execute() { return variables.strategy.execute(); }}Testing Services
Section titled “Testing Services”Unit Testing Services
Section titled “Unit Testing Services”component extends="wheels.Testbox" {
function beforeAll() { // Create service instance variables.templateService = createMock("wheels.cli.models.TemplateService"); }
function run() { describe("TemplateService", function() {
it("loads templates correctly", function() { var template = templateService.getTemplate("model"); expect(template).toBeString(); expect(template).toInclude("component extends=""Model"""); });
it("substitutes variables", function() { var result = templateService.populateTemplate( "Hello {{name}}", {name: "World"} ); expect(result).toBe("Hello World"); });
}); }}Mocking Dependencies
Section titled “Mocking Dependencies”function beforeAll() { // Create mock mockFileService = createEmptyMock("FileService");
// Define behavior mockFileService.$("writeFile").$results(true);
// Inject mock templateService.$property( propertyName = "fileService", mock = mockFileService );}Best Practices
Section titled “Best Practices”1. Single Responsibility
Section titled “1. Single Responsibility”Each service should have one clear purpose:
// Good: Focused servicecomponent name="ValidationService" { function validateModel() {} function validateController() {}}
// Bad: Mixed responsibilitiescomponent name="UtilityService" { function validateModel() {} function sendEmail() {} function generatePDF() {}}2. Dependency Injection
Section titled “2. Dependency Injection”Always inject dependencies:
// Good: Injected dependencyproperty name="fileService" inject="FileService@wheels-cli";
// Bad: Direct instantiationvariables.fileService = new FileService();3. Error Handling
Section titled “3. Error Handling”Services should handle errors gracefully:
function generateFile(required string path) { try { // Attempt operation fileWrite(arguments.path, content); return {success: true}; } catch (any e) { // Log error logError(e); // Return error info return { success: false, error: e.message }; }}4. Configuration
Section titled “4. Configuration”Services should be configurable:
component { property name="settings" inject="coldbox:modulesettings:wheels-cli";
function getTimeout() { return variables.settings.timeout ?: 30; }}Service Communication
Section titled “Service Communication”Event-Driven
Section titled “Event-Driven”Services can emit events:
// In serviceannounce("wheels-cli:modelGenerated", {model: modelName});
// In listenerfunction onModelGenerated(event, data) { // React to event}Direct Communication
Section titled “Direct Communication”Services can call each other:
component { property name="validationService" inject="ValidationService@wheels-cli";
function generateModel() { // Validate first if (!validationService.isValidModelName(name)) { throw("Invalid model name"); } // Continue generation }}Extending Services
Section titled “Extending Services”Creating Service Plugins
Section titled “Creating Service Plugins”component implements="IServicePlugin" {
function enhance(required component service) { // Add new method arguments.service.myNewMethod = function() { return "Enhanced!"; }; }}Service Decorators
Section titled “Service Decorators”component extends="BaseService" {
property name="decoratedService";
function init(required component service) { variables.decoratedService = arguments.service; return this; }
function doSomething() { // Add behavior log("Calling doSomething"); // Delegate return variables.decoratedService.doSomething(); }}Performance Considerations
Section titled “Performance Considerations”1. Lazy Loading
Section titled “1. Lazy Loading”Load services only when needed:
function getTemplateService() { if (!structKeyExists(variables, "templateService")) { variables.templateService = getInstance("TemplateService@wheels-cli"); } return variables.templateService;}2. Caching
Section titled “2. Caching”Cache expensive operations:
component { property name="cache" default={};
function getTemplate(required string name) { if (!structKeyExists(variables.cache, arguments.name)) { variables.cache[arguments.name] = loadTemplate(arguments.name); } return variables.cache[arguments.name]; }}3. Async Operations
Section titled “3. Async Operations”Use async for long-running tasks:
function analyzeLargeCodebase() { thread name="analysis-#createUUID()#" { // Long running analysis }}Debugging Services
Section titled “Debugging Services”Enable Debug Logging
Section titled “Enable Debug Logging”component { property name="log" inject="logbox:logger:{this}";
function doSomething() { log.debug("Starting doSomething with args: #serializeJSON(arguments)#"); // ... logic ... log.debug("Completed doSomething"); }}Service Inspection
Section titled “Service Inspection”# In CommandBox REPLrepl> getInstance("TemplateService@wheels-cli")repl> getMetadata(getInstance("TemplateService@wheels-cli"))Future Enhancements
Section titled “Future Enhancements”Planned service architecture improvements:
- Service Mesh: Inter-service communication layer
- Service Discovery: Dynamic service registration
- Circuit Breakers: Fault tolerance patterns
- Service Metrics: Performance monitoring
- API Gateway: Unified service access