tasker-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +443 -0
- data/Rakefile +10 -0
- data/app/controllers/tasker/analytics_controller.rb +179 -0
- data/app/controllers/tasker/application_controller.rb +45 -0
- data/app/controllers/tasker/graphql_controller.rb +193 -0
- data/app/controllers/tasker/handlers_controller.rb +217 -0
- data/app/controllers/tasker/health_controller.rb +229 -0
- data/app/controllers/tasker/metrics_controller.rb +111 -0
- data/app/controllers/tasker/page_sort.rb +97 -0
- data/app/controllers/tasker/task_diagrams_controller.rb +30 -0
- data/app/controllers/tasker/tasks_controller.rb +123 -0
- data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
- data/app/graphql/examples/all_tasks.graphql +22 -0
- data/app/graphql/examples/pending_tasks.graphql +23 -0
- data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
- data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
- data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
- data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
- data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
- data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
- data/app/graphql/tasker/graph_ql_types.rb +42 -0
- data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
- data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
- data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
- data/app/graphql/tasker/mutations/create_task.rb +52 -0
- data/app/graphql/tasker/mutations/update_step.rb +36 -0
- data/app/graphql/tasker/mutations/update_task.rb +41 -0
- data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
- data/app/graphql/tasker/queries/all_tasks.rb +23 -0
- data/app/graphql/tasker/queries/base_query.rb +9 -0
- data/app/graphql/tasker/queries/helpers.rb +16 -0
- data/app/graphql/tasker/queries/one_step.rb +24 -0
- data/app/graphql/tasker/queries/one_task.rb +18 -0
- data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
- data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
- data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
- data/app/jobs/tasker/application_job.rb +8 -0
- data/app/jobs/tasker/metrics_export_job.rb +252 -0
- data/app/jobs/tasker/task_runner_job.rb +224 -0
- data/app/models/tasker/annotation_type.rb +26 -0
- data/app/models/tasker/application_record.rb +70 -0
- data/app/models/tasker/dependent_system.rb +26 -0
- data/app/models/tasker/dependent_system_object_map.rb +64 -0
- data/app/models/tasker/diagram/edge.rb +106 -0
- data/app/models/tasker/diagram/flowchart.rb +137 -0
- data/app/models/tasker/diagram/node.rb +99 -0
- data/app/models/tasker/named_step.rb +41 -0
- data/app/models/tasker/named_task.rb +121 -0
- data/app/models/tasker/named_tasks_named_step.rb +82 -0
- data/app/models/tasker/step_dag_relationship.rb +65 -0
- data/app/models/tasker/step_readiness_status.rb +59 -0
- data/app/models/tasker/task.rb +424 -0
- data/app/models/tasker/task_annotation.rb +36 -0
- data/app/models/tasker/task_diagram.rb +332 -0
- data/app/models/tasker/task_execution_context.rb +29 -0
- data/app/models/tasker/task_namespace.rb +41 -0
- data/app/models/tasker/task_transition.rb +235 -0
- data/app/models/tasker/workflow_step.rb +461 -0
- data/app/models/tasker/workflow_step_edge.rb +94 -0
- data/app/models/tasker/workflow_step_transition.rb +434 -0
- data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
- data/app/serializers/tasker/handler_serializer.rb +109 -0
- data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
- data/app/serializers/tasker/task_serializer.rb +168 -0
- data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
- data/app/services/tasker/analytics_service.rb +409 -0
- data/app/views/tasker/task/_diagram.html.erb +32 -0
- data/config/initializers/dry_struct.rb +11 -0
- data/config/initializers/statesman.rb +6 -0
- data/config/initializers/tasker_orchestration.rb +17 -0
- data/config/initializers/time_formats.rb +4 -0
- data/config/routes.rb +34 -0
- data/config/tasker/subscriptions/example_integrations.yml +67 -0
- data/config/tasker/system_events.yml +305 -0
- data/db/functions/calculate_dependency_levels_v01.sql +45 -0
- data/db/functions/get_analytics_metrics_v01.sql +137 -0
- data/db/functions/get_slowest_steps_v01.sql +82 -0
- data/db/functions/get_slowest_tasks_v01.sql +96 -0
- data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
- data/db/functions/get_step_readiness_status_v01.sql +139 -0
- data/db/functions/get_system_health_counts_v01.sql +108 -0
- data/db/functions/get_task_execution_context_v01.sql +108 -0
- data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
- data/db/init/schema.sql +2277 -0
- data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
- data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
- data/docs/APPLICATION_GENERATOR.md +384 -0
- data/docs/AUTH.md +1780 -0
- data/docs/CIRCUIT_BREAKER.md +224 -0
- data/docs/DEVELOPER_GUIDE.md +2665 -0
- data/docs/EVENT_SYSTEM.md +637 -0
- data/docs/EXECUTION_CONFIGURATION.md +341 -0
- data/docs/FLOW_CHART.md +149 -0
- data/docs/HEALTH.md +542 -0
- data/docs/METRICS.md +731 -0
- data/docs/OPTIMIZATION_PLAN.md +1479 -0
- data/docs/OVERVIEW.md +552 -0
- data/docs/QUICK_START.md +270 -0
- data/docs/REGISTRY_SYSTEMS.md +373 -0
- data/docs/REST_API.md +632 -0
- data/docs/ROADMAP.md +221 -0
- data/docs/SQL_FUNCTIONS.md +1408 -0
- data/docs/TASK_DIAGRAM.md +252 -0
- data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
- data/docs/TELEMETRY.md +795 -0
- data/docs/TROUBLESHOOTING.md +756 -0
- data/docs/TaskHandlerGenerator.html +255 -0
- data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
- data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
- data/docs/Tasker/Analysis.html +117 -0
- data/docs/Tasker/AnalyticsController.html +450 -0
- data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
- data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
- data/docs/Tasker/AnalyticsService.html +2221 -0
- data/docs/Tasker/AnnotationType.html +137 -0
- data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
- data/docs/Tasker/ApplicationController.html +147 -0
- data/docs/Tasker/ApplicationJob.html +128 -0
- data/docs/Tasker/ApplicationRecord.html +378 -0
- data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
- data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
- data/docs/Tasker/Authentication/Coordinator.html +242 -0
- data/docs/Tasker/Authentication/Interface.html +560 -0
- data/docs/Tasker/Authentication/InterfaceError.html +124 -0
- data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
- data/docs/Tasker/Authentication.html +119 -0
- data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
- data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
- data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
- data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
- data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +365 -0
- data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
- data/docs/Tasker/Authorization/ResourceRegistry.html +882 -0
- data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
- data/docs/Tasker/Authorization.html +582 -0
- data/docs/Tasker/CacheCapabilities.html +167 -0
- data/docs/Tasker/CacheStrategy.html +1297 -0
- data/docs/Tasker/Concerns/Authenticatable.html +116 -0
- data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
- data/docs/Tasker/Concerns/Authorizable.html +816 -0
- data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
- data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
- data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
- data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
- data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
- data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
- data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
- data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
- data/docs/Tasker/Concerns.html +117 -0
- data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
- data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
- data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
- data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
- data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
- data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
- data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
- data/docs/Tasker/Configuration.html +1669 -0
- data/docs/Tasker/ConfigurationError.html +143 -0
- data/docs/Tasker/ConfiguredTask.html +514 -0
- data/docs/Tasker/Constants/EventDefinitions.html +590 -0
- data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
- data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
- data/docs/Tasker/Constants/RegistryEvents.html +285 -0
- data/docs/Tasker/Constants/StepEvents.html +177 -0
- data/docs/Tasker/Constants/TaskEvents.html +167 -0
- data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
- data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
- data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
- data/docs/Tasker/Constants/TaskExecution.html +126 -0
- data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
- data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
- data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
- data/docs/Tasker/Constants/TaskFinalization.html +126 -0
- data/docs/Tasker/Constants/TaskStatuses.html +223 -0
- data/docs/Tasker/Constants/TestEvents.html +163 -0
- data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
- data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
- data/docs/Tasker/Constants.html +561 -0
- data/docs/Tasker/DependentSystem.html +137 -0
- data/docs/Tasker/DependentSystemObjectMap.html +250 -0
- data/docs/Tasker/DetectorRegistry.html +598 -0
- data/docs/Tasker/Diagram/Edge.html +1191 -0
- data/docs/Tasker/Diagram/Flowchart.html +1539 -0
- data/docs/Tasker/Diagram/Node.html +1165 -0
- data/docs/Tasker/Diagram.html +117 -0
- data/docs/Tasker/Engine.html +215 -0
- data/docs/Tasker/Error.html +139 -0
- data/docs/Tasker/Events/Bus.html +1226 -0
- data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
- data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
- data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
- data/docs/Tasker/Events/Catalog.html +1291 -0
- data/docs/Tasker/Events/CustomRegistry.html +943 -0
- data/docs/Tasker/Events/DefinitionLoader.html +575 -0
- data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
- data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
- data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
- data/docs/Tasker/Events/Publisher.html +365 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
- data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
- data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
- data/docs/Tasker/Events/Subscribers.html +117 -0
- data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
- data/docs/Tasker/Events.html +294 -0
- data/docs/Tasker/EventsGenerator.html +459 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
- data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
- data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
- data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
- data/docs/Tasker/Functions.html +117 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
- data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
- data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
- data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
- data/docs/Tasker/Generators.html +117 -0
- data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
- data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
- data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
- data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
- data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
- data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
- data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
- data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
- data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
- data/docs/Tasker/GraphQLTypes.html +130 -0
- data/docs/Tasker/GraphqlController.html +251 -0
- data/docs/Tasker/HandlerFactory.html +1518 -0
- data/docs/Tasker/HandlerSerializer.html +682 -0
- data/docs/Tasker/HandlersController.html +574 -0
- data/docs/Tasker/HashIdentityStrategy.html +278 -0
- data/docs/Tasker/Health/ReadinessChecker.html +712 -0
- data/docs/Tasker/Health/StatusChecker.html +653 -0
- data/docs/Tasker/Health.html +117 -0
- data/docs/Tasker/HealthController.html +523 -0
- data/docs/Tasker/IdentityStrategy.html +276 -0
- data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
- data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events.html +204 -0
- data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
- data/docs/Tasker/LifecycleEvents.html +799 -0
- data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
- data/docs/Tasker/Logging.html +115 -0
- data/docs/Tasker/MetricsController.html +293 -0
- data/docs/Tasker/MetricsExportJob.html +414 -0
- data/docs/Tasker/Mutations/BaseMutation.html +128 -0
- data/docs/Tasker/Mutations/CancelStep.html +219 -0
- data/docs/Tasker/Mutations/CancelTask.html +221 -0
- data/docs/Tasker/Mutations/CreateTask.html +243 -0
- data/docs/Tasker/Mutations/UpdateStep.html +243 -0
- data/docs/Tasker/Mutations/UpdateTask.html +243 -0
- data/docs/Tasker/Mutations.html +117 -0
- data/docs/Tasker/NamedStep.html +216 -0
- data/docs/Tasker/NamedTask.html +910 -0
- data/docs/Tasker/NamedTasksNamedStep.html +435 -0
- data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
- data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
- data/docs/Tasker/Orchestration/Coordinator.html +641 -0
- data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
- data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
- data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
- data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
- data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
- data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
- data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
- data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
- data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
- data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
- data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
- data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
- data/docs/Tasker/Orchestration.html +130 -0
- data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
- data/docs/Tasker/PageSort.html +247 -0
- data/docs/Tasker/PermanentError.html +518 -0
- data/docs/Tasker/ProceduralError.html +147 -0
- data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
- data/docs/Tasker/Queries/AllTasks.html +221 -0
- data/docs/Tasker/Queries/BaseQuery.html +128 -0
- data/docs/Tasker/Queries/Helpers.html +187 -0
- data/docs/Tasker/Queries/OneStep.html +225 -0
- data/docs/Tasker/Queries/OneTask.html +217 -0
- data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
- data/docs/Tasker/Queries/TasksByStatus.html +233 -0
- data/docs/Tasker/Queries.html +119 -0
- data/docs/Tasker/Railtie.html +124 -0
- data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
- data/docs/Tasker/Registry/EventPublisher.html +667 -0
- data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
- data/docs/Tasker/Registry/RegistrationError.html +132 -0
- data/docs/Tasker/Registry/RegistryError.html +139 -0
- data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
- data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
- data/docs/Tasker/Registry/ValidationError.html +132 -0
- data/docs/Tasker/Registry.html +119 -0
- data/docs/Tasker/RetryableError.html +515 -0
- data/docs/Tasker/StateMachine/Compatibility.html +282 -0
- data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
- data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
- data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
- data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
- data/docs/Tasker/StateMachine.html +602 -0
- data/docs/Tasker/StepDagRelationship.html +657 -0
- data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
- data/docs/Tasker/StepHandler/Api.html +884 -0
- data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
- data/docs/Tasker/StepHandler/Base.html +970 -0
- data/docs/Tasker/StepHandler.html +119 -0
- data/docs/Tasker/StepReadinessStatus.html +836 -0
- data/docs/Tasker/Task.html +2575 -0
- data/docs/Tasker/TaskAnnotation.html +137 -0
- data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
- data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
- data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
- data/docs/Tasker/TaskBuilder.html +764 -0
- data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
- data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
- data/docs/Tasker/TaskDiagram.html +548 -0
- data/docs/Tasker/TaskDiagramsController.html +240 -0
- data/docs/Tasker/TaskExecutionContext.html +469 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
- data/docs/Tasker/TaskHandler/ClassMethods.html +357 -0
- data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
- data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
- data/docs/Tasker/TaskHandler.html +271 -0
- data/docs/Tasker/TaskNamespace.html +312 -0
- data/docs/Tasker/TaskRunnerJob.html +406 -0
- data/docs/Tasker/TaskSerializer.html +474 -0
- data/docs/Tasker/TaskTransition.html +1517 -0
- data/docs/Tasker/TaskWorkflowSummary.html +988 -0
- data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
- data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema.html +384 -0
- data/docs/Tasker/TasksController.html +595 -0
- data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
- data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
- data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
- data/docs/Tasker/Telemetry/Events.html +115 -0
- data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
- data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
- data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
- data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
- data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
- data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
- data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
- data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
- data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
- data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
- data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
- data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
- data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
- data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
- data/docs/Tasker/Telemetry/Plugins.html +117 -0
- data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
- data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
- data/docs/Tasker/Telemetry.html +130 -0
- data/docs/Tasker/Types/AuthConfig.html +886 -0
- data/docs/Tasker/Types/BackoffConfig.html +1063 -0
- data/docs/Tasker/Types/BaseConfig.html +227 -0
- data/docs/Tasker/Types/CacheConfig.html +1731 -0
- data/docs/Tasker/Types/DatabaseConfig.html +388 -0
- data/docs/Tasker/Types/DependencyGraph.html +526 -0
- data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
- data/docs/Tasker/Types/EngineConfig.html +1181 -0
- data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
- data/docs/Tasker/Types/GraphEdge.html +517 -0
- data/docs/Tasker/Types/GraphMetadata.html +781 -0
- data/docs/Tasker/Types/GraphNode.html +694 -0
- data/docs/Tasker/Types/HealthConfig.html +784 -0
- data/docs/Tasker/Types/StepSequence.html +353 -0
- data/docs/Tasker/Types/StepTemplate.html +1193 -0
- data/docs/Tasker/Types/TaskRequest.html +1179 -0
- data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
- data/docs/Tasker/Types.html +154 -0
- data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
- data/docs/Tasker/WorkflowStep.html +2724 -0
- data/docs/Tasker/WorkflowStepEdge.html +304 -0
- data/docs/Tasker/WorkflowStepSerializer.html +305 -0
- data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
- data/docs/Tasker/WorkflowStepTransition.html +2201 -0
- data/docs/Tasker/WorkflowStepsController.html +462 -0
- data/docs/Tasker.html +452 -0
- data/docs/VISION.md +584 -0
- data/docs/WHY.md +21 -0
- data/docs/_index.html +2375 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/events/migration_plan_outcomes.md +80 -0
- data/docs/file.README.html +541 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +541 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +9182 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/generators/tasker/authenticator_generator.rb +301 -0
- data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
- data/lib/generators/tasker/events_generator.rb +91 -0
- data/lib/generators/tasker/subscriber_generator.rb +107 -0
- data/lib/generators/tasker/task_handler_generator.rb +138 -0
- data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
- data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
- data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +95 -0
- data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +142 -0
- data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
- data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
- data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
- data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
- data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
- data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
- data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
- data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
- data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
- data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
- data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
- data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
- data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
- data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
- data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
- data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
- data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
- data/lib/generators/tasker/templates/task_handler.rb.erb +59 -0
- data/lib/generators/tasker/templates/task_handler_spec.rb.erb +159 -0
- data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
- data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
- data/lib/tasker/authentication/coordinator.rb +78 -0
- data/lib/tasker/authentication/errors.rb +9 -0
- data/lib/tasker/authentication/interface.rb +36 -0
- data/lib/tasker/authentication/none_authenticator.rb +26 -0
- data/lib/tasker/authorization/base_coordinator.rb +112 -0
- data/lib/tasker/authorization/errors.rb +26 -0
- data/lib/tasker/authorization/resource_constants.rb +74 -0
- data/lib/tasker/authorization/resource_registry.rb +143 -0
- data/lib/tasker/authorization.rb +75 -0
- data/lib/tasker/cache_capabilities.rb +131 -0
- data/lib/tasker/cache_strategy.rb +469 -0
- data/lib/tasker/concerns/authenticatable.rb +41 -0
- data/lib/tasker/concerns/authorizable.rb +204 -0
- data/lib/tasker/concerns/controller_authorizable.rb +124 -0
- data/lib/tasker/concerns/event_publisher.rb +716 -0
- data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
- data/lib/tasker/concerns/state_machine_base.rb +218 -0
- data/lib/tasker/concerns/structured_logging.rb +387 -0
- data/lib/tasker/configuration.rb +325 -0
- data/lib/tasker/constants/event_definitions.rb +147 -0
- data/lib/tasker/constants/registry_events.rb +54 -0
- data/lib/tasker/constants.rb +417 -0
- data/lib/tasker/engine.rb +90 -0
- data/lib/tasker/errors.rb +90 -0
- data/lib/tasker/events/catalog.rb +432 -0
- data/lib/tasker/events/custom_registry.rb +175 -0
- data/lib/tasker/events/definition_loader.rb +199 -0
- data/lib/tasker/events/event_payload_builder.rb +461 -0
- data/lib/tasker/events/publisher.rb +149 -0
- data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
- data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
- data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
- data/lib/tasker/events/subscription_loader.rb +161 -0
- data/lib/tasker/events.rb +37 -0
- data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
- data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
- data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
- data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
- data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
- data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
- data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
- data/lib/tasker/functions/function_wrapper.rb +42 -0
- data/lib/tasker/functions.rb +12 -0
- data/lib/tasker/handler_factory.rb +322 -0
- data/lib/tasker/health/readiness_checker.rb +186 -0
- data/lib/tasker/health/status_checker.rb +203 -0
- data/lib/tasker/identity_strategy.rb +38 -0
- data/lib/tasker/logging/correlation_id_generator.rb +120 -0
- data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
- data/lib/tasker/orchestration/connection_builder.rb +122 -0
- data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
- data/lib/tasker/orchestration/coordinator.rb +119 -0
- data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
- data/lib/tasker/orchestration/plugin_integration.rb +124 -0
- data/lib/tasker/orchestration/response_processor.rb +168 -0
- data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
- data/lib/tasker/orchestration/step_executor.rb +941 -0
- data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
- data/lib/tasker/orchestration/task_finalizer.rb +564 -0
- data/lib/tasker/orchestration/task_initializer.rb +140 -0
- data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
- data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
- data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
- data/lib/tasker/orchestration.rb +45 -0
- data/lib/tasker/railtie.rb +9 -0
- data/lib/tasker/registry/base_registry.rb +177 -0
- data/lib/tasker/registry/event_publisher.rb +91 -0
- data/lib/tasker/registry/interface_validator.rb +140 -0
- data/lib/tasker/registry/statistics_collector.rb +381 -0
- data/lib/tasker/registry/subscriber_registry.rb +285 -0
- data/lib/tasker/registry.rb +22 -0
- data/lib/tasker/state_machine/step_state_machine.rb +508 -0
- data/lib/tasker/state_machine/task_state_machine.rb +192 -0
- data/lib/tasker/state_machine.rb +83 -0
- data/lib/tasker/step_handler/api.rb +410 -0
- data/lib/tasker/step_handler/base.rb +206 -0
- data/lib/tasker/task_builder.rb +432 -0
- data/lib/tasker/task_handler/class_methods.rb +324 -0
- data/lib/tasker/task_handler/instance_methods.rb +293 -0
- data/lib/tasker/task_handler/step_group.rb +182 -0
- data/lib/tasker/task_handler.rb +43 -0
- data/lib/tasker/telemetry/event_mapping.rb +126 -0
- data/lib/tasker/telemetry/event_router.rb +318 -0
- data/lib/tasker/telemetry/events/export_events.rb +38 -0
- data/lib/tasker/telemetry/export_coordinator.rb +497 -0
- data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
- data/lib/tasker/telemetry/log_backend.rb +224 -0
- data/lib/tasker/telemetry/metric_types.rb +368 -0
- data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
- data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
- data/lib/tasker/telemetry/plugin_registry.rb +333 -0
- data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
- data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
- data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
- data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
- data/lib/tasker/telemetry/trace_backend.rb +186 -0
- data/lib/tasker/telemetry.rb +59 -0
- data/lib/tasker/types/auth_config.rb +81 -0
- data/lib/tasker/types/backoff_config.rb +142 -0
- data/lib/tasker/types/cache_config.rb +257 -0
- data/lib/tasker/types/database_config.rb +39 -0
- data/lib/tasker/types/dependency_graph.rb +225 -0
- data/lib/tasker/types/dependency_graph_config.rb +149 -0
- data/lib/tasker/types/engine_config.rb +131 -0
- data/lib/tasker/types/execution_config.rb +289 -0
- data/lib/tasker/types/health_config.rb +84 -0
- data/lib/tasker/types/step_sequence.rb +24 -0
- data/lib/tasker/types/step_template.rb +63 -0
- data/lib/tasker/types/task_request.rb +60 -0
- data/lib/tasker/types/telemetry_config.rb +273 -0
- data/lib/tasker/types.rb +64 -0
- data/lib/tasker/version.rb +7 -0
- data/lib/tasker.rb +82 -0
- data/lib/tasks/tasker_tasks.rake +302 -0
- metadata +958 -0
@@ -0,0 +1,2665 @@
|
|
1
|
+
# Tasker Developer Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
This guide provides a comprehensive overview of developing with Tasker, covering all the key components that make up the workflow engine. Tasker is designed around six main developer-facing components:
|
6
|
+
|
7
|
+
1. **TaskNamespaces & Versioning** - Organize and version task handlers for scalable workflow management
|
8
|
+
2. **Task Handlers** - Define and coordinate multi-step workflows
|
9
|
+
3. **Step Handlers** - Implement the business logic for individual workflow steps
|
10
|
+
4. **Event Subscribers** - Create integrations with external services and monitoring systems
|
11
|
+
5. **YAML Configuration** - Declarative workflow and step configuration with namespace support
|
12
|
+
6. **Authentication & Authorization** - Secure your workflows with flexible authentication strategies
|
13
|
+
|
14
|
+
## New in Tasker 2.3.0: TaskNamespace + Versioning Architecture
|
15
|
+
|
16
|
+
Tasker now supports **hierarchical task organization** and **semantic versioning** for enterprise-scale workflow management:
|
17
|
+
|
18
|
+
### Key Benefits
|
19
|
+
- **Namespace Isolation**: Organize tasks by domain (`payments`, `inventory`, `notifications`)
|
20
|
+
- **Version Coexistence**: Multiple versions of the same task can run simultaneously
|
21
|
+
- **Zero Breaking Changes**: Existing tasks continue working with automatic defaults
|
22
|
+
- **Enterprise Scalability**: Clean separation of concerns for large organizations
|
23
|
+
|
24
|
+
### Quick Example
|
25
|
+
```ruby
|
26
|
+
# Create namespaced task handlers
|
27
|
+
task_request = Tasker::Types::TaskRequest.new(
|
28
|
+
name: 'process_order',
|
29
|
+
namespace: 'payments', # NEW: Namespace organization
|
30
|
+
version: '2.1.0', # NEW: Semantic versioning
|
31
|
+
context: { order_id: 123 }
|
32
|
+
)
|
33
|
+
|
34
|
+
# Handler lookup with namespace + version
|
35
|
+
handler = Tasker::HandlerFactory.instance.get(
|
36
|
+
'process_order',
|
37
|
+
namespace_name: 'payments',
|
38
|
+
version: '2.1.0'
|
39
|
+
)
|
40
|
+
```
|
41
|
+
|
42
|
+
### Namespace Organization Patterns
|
43
|
+
- **`payments`** - Payment processing, billing, refunds
|
44
|
+
- **`inventory`** - Stock management, fulfillment, warehouse operations
|
45
|
+
- **`notifications`** - Email, SMS, push notifications, alerts
|
46
|
+
- **`integrations`** - Third-party APIs, webhooks, data synchronization
|
47
|
+
- **`data_processing`** - ETL, data transformation, analytics pipelines
|
48
|
+
- **`default`** - General workflows (automatic fallback when unspecified)
|
49
|
+
|
50
|
+
## Architecture Overview
|
51
|
+
|
52
|
+
```mermaid
|
53
|
+
flowchart TB
|
54
|
+
subgraph Config["YAML Configuration"]
|
55
|
+
TaskYAML["Task Handler YAML<br/>config/tasker/tasks/"]
|
56
|
+
StepConfig["Step Configuration<br/>Dependencies & Settings"]
|
57
|
+
end
|
58
|
+
|
59
|
+
subgraph Handlers["Handler Classes"]
|
60
|
+
TaskHandler["Task Handler<br/>app/tasks/"]
|
61
|
+
StepHandler["Step Handler<br/>app/tasks/*/step_handler/"]
|
62
|
+
end
|
63
|
+
|
64
|
+
subgraph Events["Event System"]
|
65
|
+
EventCatalog["Event Catalog<br/>Tasker::Events.catalog"]
|
66
|
+
Subscribers["Event Subscribers<br/>app/subscribers/"]
|
67
|
+
end
|
68
|
+
|
69
|
+
subgraph External["External Integrations"]
|
70
|
+
Sentry["Error Tracking<br/>(Sentry)"]
|
71
|
+
Metrics["Metrics<br/>(DataDog/StatsD)"]
|
72
|
+
Alerts["Alerting<br/>(PagerDuty)"]
|
73
|
+
Notifications["Notifications<br/>(Slack/Email)"]
|
74
|
+
end
|
75
|
+
|
76
|
+
TaskYAML --> TaskHandler
|
77
|
+
StepConfig --> StepHandler
|
78
|
+
TaskHandler --> Events
|
79
|
+
StepHandler --> Events
|
80
|
+
EventCatalog --> Subscribers
|
81
|
+
Subscribers --> Sentry
|
82
|
+
Subscribers --> Metrics
|
83
|
+
Subscribers --> Alerts
|
84
|
+
Subscribers --> Notifications
|
85
|
+
|
86
|
+
classDef config fill:#e1f5fe,stroke:#01579b
|
87
|
+
classDef handlers fill:#f3e5f5,stroke:#4a148c
|
88
|
+
classDef events fill:#fff3e0,stroke:#e65100
|
89
|
+
classDef external fill:#e8f5e8,stroke:#2e7d32
|
90
|
+
|
91
|
+
class TaskYAML,StepConfig config
|
92
|
+
class TaskHandler,StepHandler handlers
|
93
|
+
class EventCatalog,Subscribers events
|
94
|
+
class Sentry,Metrics,Alerts,Notifications external
|
95
|
+
```
|
96
|
+
|
97
|
+
## 1. TaskNamespaces & Versioning
|
98
|
+
|
99
|
+
TaskNamespaces provide organizational hierarchy for task handlers, while versioning enables multiple versions of the same task to coexist. This is essential for enterprise environments with complex workflows and deployment strategies.
|
100
|
+
|
101
|
+
### TaskNamespace Management
|
102
|
+
|
103
|
+
#### Creating TaskNamespaces
|
104
|
+
|
105
|
+
TaskNamespaces are automatically created when referenced, but you can also create them explicitly:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# Automatic creation during task creation
|
109
|
+
task_request = Tasker::Types::TaskRequest.new(
|
110
|
+
name: 'process_payment',
|
111
|
+
namespace: 'payments', # Automatically creates 'payments' namespace if needed
|
112
|
+
version: '1.0.0',
|
113
|
+
context: { payment_id: 123 }
|
114
|
+
)
|
115
|
+
|
116
|
+
# Manual creation with description
|
117
|
+
namespace = Tasker::TaskNamespace.create!(
|
118
|
+
name: 'payments',
|
119
|
+
description: 'Payment processing and billing workflows'
|
120
|
+
)
|
121
|
+
```
|
122
|
+
|
123
|
+
#### Namespace Patterns & Best Practices
|
124
|
+
|
125
|
+
**Domain-Based Organization**:
|
126
|
+
```yaml
|
127
|
+
# config/tasker/tasks/payments/process_order.yaml
|
128
|
+
---
|
129
|
+
name: process_order
|
130
|
+
namespace_name: payments
|
131
|
+
version: 2.1.0
|
132
|
+
task_handler_class: Payments::ProcessOrderHandler
|
133
|
+
|
134
|
+
# config/tasker/tasks/inventory/process_order.yaml
|
135
|
+
---
|
136
|
+
name: process_order
|
137
|
+
namespace_name: inventory
|
138
|
+
version: 1.5.0
|
139
|
+
task_handler_class: Inventory::ProcessOrderHandler
|
140
|
+
```
|
141
|
+
|
142
|
+
**Version Management Strategies**:
|
143
|
+
```ruby
|
144
|
+
# Gradual rollout - start with specific version
|
145
|
+
handler_v2 = Tasker::HandlerFactory.instance.get(
|
146
|
+
'process_payment',
|
147
|
+
namespace_name: 'payments',
|
148
|
+
version: '2.0.0'
|
149
|
+
)
|
150
|
+
|
151
|
+
# Legacy support - maintain old version
|
152
|
+
handler_v1 = Tasker::HandlerFactory.instance.get(
|
153
|
+
'process_payment',
|
154
|
+
namespace_name: 'payments',
|
155
|
+
version: '1.5.0'
|
156
|
+
)
|
157
|
+
|
158
|
+
# Default behavior - uses latest registered version
|
159
|
+
handler_default = Tasker::HandlerFactory.instance.get('process_payment')
|
160
|
+
```
|
161
|
+
|
162
|
+
#### HandlerFactory Registry Architecture
|
163
|
+
|
164
|
+
The HandlerFactory now uses a **thread-safe 3-level registry** with enterprise-grade capabilities:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# Registry Structure: namespace_name → handler_name → version → handler_class
|
168
|
+
# Thread-safe storage: Concurrent::Hash for all levels
|
169
|
+
# Example internal structure:
|
170
|
+
{
|
171
|
+
payments: {
|
172
|
+
process_order: {
|
173
|
+
'1.0.0' => Payments::ProcessOrderV1,
|
174
|
+
'2.0.0' => Payments::ProcessOrderV2
|
175
|
+
}
|
176
|
+
},
|
177
|
+
inventory: {
|
178
|
+
process_order: {
|
179
|
+
'1.5.0' => Inventory::ProcessOrder
|
180
|
+
}
|
181
|
+
}
|
182
|
+
}
|
183
|
+
```
|
184
|
+
|
185
|
+
**Enterprise Features**:
|
186
|
+
- **Thread-Safe Operations**: `Concurrent::Hash` storage eliminates race conditions
|
187
|
+
- **Structured Logging**: Every operation logged with correlation IDs
|
188
|
+
- **Interface Validation**: Fail-fast validation with detailed error messages
|
189
|
+
- **Conflict Resolution**: `replace: true` parameter for graceful updates
|
190
|
+
- **Health Monitoring**: Built-in statistics and health checks
|
191
|
+
- **Event Integration**: Registry operations trigger 56-event system
|
192
|
+
|
193
|
+
**Registration Examples**:
|
194
|
+
```ruby
|
195
|
+
# Class-based registration with namespace + version
|
196
|
+
class PaymentHandler < Tasker::TaskHandler
|
197
|
+
register_handler(
|
198
|
+
'process_payment',
|
199
|
+
namespace_name: 'payments',
|
200
|
+
version: '2.0.0'
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Thread-safe manual registration with conflict resolution
|
205
|
+
Tasker::HandlerFactory.instance.register(
|
206
|
+
'payment_processor',
|
207
|
+
PaymentHandler,
|
208
|
+
namespace_name: 'payments',
|
209
|
+
version: '2.1.0',
|
210
|
+
replace: true # Gracefully handles conflicts
|
211
|
+
)
|
212
|
+
|
213
|
+
# Automatic structured logging output:
|
214
|
+
# {"correlation_id":"tsk_abc123","component":"handler_factory","message":"Registry item registered","entity_id":"payments/payment_processor/2.1.0","event_type":"registered"}
|
215
|
+
|
216
|
+
# YAML-based registration
|
217
|
+
# config/tasker/tasks/payments/process_payment.yaml
|
218
|
+
---
|
219
|
+
name: process_payment
|
220
|
+
namespace_name: payments
|
221
|
+
version: 2.0.0
|
222
|
+
task_handler_class: PaymentHandler
|
223
|
+
```
|
224
|
+
|
225
|
+
### Versioning Best Practices
|
226
|
+
|
227
|
+
#### Semantic Versioning
|
228
|
+
Follow [semver.org](https://semver.org) conventions:
|
229
|
+
- **Major version** (`2.0.0`): Breaking changes, incompatible API changes
|
230
|
+
- **Minor version** (`1.1.0`): New features, backward compatible
|
231
|
+
- **Patch version** (`1.0.1`): Bug fixes, backward compatible
|
232
|
+
|
233
|
+
#### Version Lifecycle Management
|
234
|
+
```ruby
|
235
|
+
# Development workflow
|
236
|
+
class PaymentHandler < Tasker::TaskHandler
|
237
|
+
register_handler('process_payment',
|
238
|
+
namespace_name: 'payments',
|
239
|
+
version: '2.0.0-beta.1') # Pre-release
|
240
|
+
end
|
241
|
+
|
242
|
+
# Production deployment
|
243
|
+
class PaymentHandler < Tasker::TaskHandler
|
244
|
+
register_handler('process_payment',
|
245
|
+
namespace_name: 'payments',
|
246
|
+
version: '2.0.0') # Stable release
|
247
|
+
end
|
248
|
+
|
249
|
+
# Maintenance mode
|
250
|
+
class PaymentHandler < Tasker::TaskHandler
|
251
|
+
register_handler('process_payment',
|
252
|
+
namespace_name: 'payments',
|
253
|
+
version: '1.5.2') # Patch release for legacy
|
254
|
+
end
|
255
|
+
```
|
256
|
+
|
257
|
+
## 2. Task Handlers
|
258
|
+
|
259
|
+
Task handlers define the overall workflow structure and coordinate step execution. They are the entry point for creating and managing multi-step processes.
|
260
|
+
|
261
|
+
### Creating Task Handlers
|
262
|
+
|
263
|
+
Use the generator to create a complete task handler structure:
|
264
|
+
|
265
|
+
```bash
|
266
|
+
rails generate tasker:task_handler OrderHandler --module_namespace OrderProcess
|
267
|
+
```
|
268
|
+
|
269
|
+
This creates:
|
270
|
+
- **Handler Class**: `app/tasks/order_process/order_handler.rb`
|
271
|
+
- **YAML Configuration**: `config/tasker/tasks/order_process/order_handler.yaml`
|
272
|
+
- **Test File**: `spec/tasks/order_process/order_handler_spec.rb`
|
273
|
+
|
274
|
+
### Task Handler Class Structure
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
# app/tasks/order_process/order_handler.rb
|
278
|
+
module OrderProcess
|
279
|
+
class OrderHandler < Tasker::ConfiguredTask
|
280
|
+
# The task handler class is primarily configuration-driven
|
281
|
+
# Most behavior is defined in the YAML file
|
282
|
+
|
283
|
+
# Optional: Override the default task name (defaults to class name underscored)
|
284
|
+
def self.task_name
|
285
|
+
'custom_order_process'
|
286
|
+
end
|
287
|
+
|
288
|
+
# Optional: Override the default YAML path
|
289
|
+
def self.yaml_path
|
290
|
+
Rails.root.join('config/custom_tasks/order_handler.yaml')
|
291
|
+
end
|
292
|
+
|
293
|
+
# Optional: Establish custom step dependencies beyond YAML configuration
|
294
|
+
def establish_step_dependencies_and_defaults(task, steps)
|
295
|
+
# Add runtime dependencies based on task context
|
296
|
+
if task.context['priority'] == 'expedited'
|
297
|
+
# Modify step configuration for expedited orders
|
298
|
+
payment_step = steps.find { |s| s.name == 'process_payment' }
|
299
|
+
payment_step&.update(retry_limit: 1) # Faster failure for expedited orders
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Optional: Update annotations after steps complete
|
304
|
+
def update_annotations(task, sequence, steps)
|
305
|
+
# Add custom annotations based on step results
|
306
|
+
total_amount = steps.find { |s| s.name == 'calculate_total' }&.results&.dig('amount')
|
307
|
+
if total_amount && total_amount > 1000
|
308
|
+
task.annotations.create!(
|
309
|
+
annotation_type: 'high_value_order',
|
310
|
+
content: { amount: total_amount, flagged_at: Time.current }
|
311
|
+
)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# Optional: Custom validation schema (beyond YAML schema)
|
316
|
+
def schema
|
317
|
+
# This overrides any schema defined in YAML
|
318
|
+
{
|
319
|
+
type: 'object',
|
320
|
+
required: ['order_id', 'customer_id'],
|
321
|
+
properties: {
|
322
|
+
order_id: { type: 'integer', minimum: 1 },
|
323
|
+
customer_id: { type: 'integer', minimum: 1 },
|
324
|
+
priority: { type: 'string', enum: ['standard', 'expedited', 'rush'] }
|
325
|
+
}
|
326
|
+
}
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
```
|
331
|
+
|
332
|
+
### Task Handler Capabilities
|
333
|
+
|
334
|
+
- **Workflow Orchestration**: Manages step dependencies and execution order
|
335
|
+
- **Parallel Processing**: Supports concurrent execution of independent steps
|
336
|
+
- **Error Handling**: Comprehensive retry logic with exponential backoff
|
337
|
+
- **Context Management**: Passes data between steps through task context
|
338
|
+
- **Validation**: JSON schema validation plus custom business rules
|
339
|
+
- **Event Publishing**: Automatic lifecycle event generation
|
340
|
+
|
341
|
+
### Available Override Methods
|
342
|
+
|
343
|
+
**ConfiguredTask Class Methods** (for customizing configuration):
|
344
|
+
- `self.task_name` - Override the default task name (defaults to class name underscored)
|
345
|
+
- `self.yaml_path` - Override the default YAML configuration file path
|
346
|
+
- `self.config` - Override how configuration is loaded (defaults to YAML.load_file)
|
347
|
+
|
348
|
+
**TaskHandler Instance Methods** (for customizing behavior):
|
349
|
+
- `establish_step_dependencies_and_defaults(task, steps)` - Modify step configuration at runtime
|
350
|
+
- `update_annotations(task, sequence, steps)` - Add custom annotations after step completion
|
351
|
+
- `schema` - Define custom validation schema for task context (overrides YAML schema)
|
352
|
+
|
353
|
+
## 2. Step Handlers
|
354
|
+
|
355
|
+
Step handlers implement the specific business logic for individual workflow steps. They are the workhorses that perform the actual operations.
|
356
|
+
|
357
|
+
### Step Handler Types
|
358
|
+
|
359
|
+
**Base Step Handler** - For general business logic with custom events:
|
360
|
+
```ruby
|
361
|
+
module OrderProcess
|
362
|
+
module StepHandler
|
363
|
+
class ProcessPaymentHandler < Tasker::StepHandler::Base
|
364
|
+
# Define custom events that this handler can publish
|
365
|
+
# These are automatically registered when the task handler is loaded
|
366
|
+
def self.custom_event_configuration
|
367
|
+
[
|
368
|
+
{
|
369
|
+
name: 'payment.processed',
|
370
|
+
description: 'Published when payment processing completes successfully'
|
371
|
+
},
|
372
|
+
{
|
373
|
+
name: 'payment.risk_flagged',
|
374
|
+
description: 'Published when payment is flagged for manual review'
|
375
|
+
}
|
376
|
+
]
|
377
|
+
end
|
378
|
+
|
379
|
+
def process(task, sequence, step)
|
380
|
+
order_id = task.context[:order_id]
|
381
|
+
payment_amount = task.context[:payment_amount]
|
382
|
+
|
383
|
+
# Perform risk assessment
|
384
|
+
risk_score = assess_payment_risk(order_id, payment_amount)
|
385
|
+
|
386
|
+
if risk_score > 0.8
|
387
|
+
# Publish custom event for high-risk payments
|
388
|
+
publish_custom_event('payment.risk_flagged', {
|
389
|
+
order_id: order_id,
|
390
|
+
risk_score: risk_score,
|
391
|
+
requires_manual_review: true,
|
392
|
+
flagged_at: Time.current
|
393
|
+
})
|
394
|
+
|
395
|
+
{ status: 'risk_review', risk_score: risk_score }
|
396
|
+
else
|
397
|
+
# Process payment normally
|
398
|
+
payment_result = process_payment_transaction(order_id, payment_amount)
|
399
|
+
|
400
|
+
# Publish custom event for successful payments
|
401
|
+
publish_custom_event('payment.processed', {
|
402
|
+
order_id: order_id,
|
403
|
+
payment_amount: payment_amount,
|
404
|
+
transaction_id: payment_result[:transaction_id],
|
405
|
+
processed_at: Time.current
|
406
|
+
})
|
407
|
+
|
408
|
+
{ status: 'completed', transaction_id: payment_result[:transaction_id] }
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
private
|
413
|
+
|
414
|
+
def assess_payment_risk(order_id, amount)
|
415
|
+
# Risk assessment logic here
|
416
|
+
# Returns a score between 0.0 and 1.0
|
417
|
+
rand(0.0..1.0)
|
418
|
+
end
|
419
|
+
|
420
|
+
def process_payment_transaction(order_id, amount)
|
421
|
+
# Payment processing logic here
|
422
|
+
{ transaction_id: "txn_#{SecureRandom.hex(8)}" }
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
```
|
428
|
+
|
429
|
+
**API Step Handler** - For external API integrations:
|
430
|
+
```ruby
|
431
|
+
module OrderProcess
|
432
|
+
module StepHandler
|
433
|
+
class FetchInventoryHandler < Tasker::StepHandler::Api
|
434
|
+
include OrderProcess::ApiUtils
|
435
|
+
|
436
|
+
def process(task, sequence, step)
|
437
|
+
product_ids = get_previous_step_data(sequence, 'fetch_products', 'product_ids')
|
438
|
+
|
439
|
+
# Make HTTP request (automatic retry, timeout, error handling)
|
440
|
+
connection.get('/inventory/check', { product_ids: product_ids })
|
441
|
+
end
|
442
|
+
|
443
|
+
def process_results(step, process_output, initial_results)
|
444
|
+
# Custom response processing
|
445
|
+
inventory_data = JSON.parse(process_output.body)
|
446
|
+
step.results = {
|
447
|
+
inventory_levels: inventory_data['levels'],
|
448
|
+
availability: inventory_data['available'],
|
449
|
+
last_updated: inventory_data['timestamp']
|
450
|
+
}
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
```
|
456
|
+
|
457
|
+
### API Step Handler Implementation
|
458
|
+
|
459
|
+
**The `process` Method - Your Extension Point**:
|
460
|
+
|
461
|
+
API step handlers use the `process` method as the developer extension point. The framework handles all orchestration, error handling, retries, and event publishing automatically:
|
462
|
+
|
463
|
+
```ruby
|
464
|
+
module OrderProcess
|
465
|
+
module StepHandler
|
466
|
+
class FetchUserProfileHandler < Tasker::StepHandler::Api
|
467
|
+
include OrderProcess::ApiUtils
|
468
|
+
|
469
|
+
def process(task, sequence, step)
|
470
|
+
# Simply implement your HTTP request logic
|
471
|
+
# Framework handles retries, timeouts, and error handling automatically
|
472
|
+
user_id = task.context['user_id']
|
473
|
+
connection.get("/users/#{user_id}/profile")
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
```
|
479
|
+
|
480
|
+
**Making Different Types of API Requests**:
|
481
|
+
|
482
|
+
```ruby
|
483
|
+
module OrderProcess
|
484
|
+
module StepHandler
|
485
|
+
# GET request example
|
486
|
+
class FetchOrderHandler < Tasker::StepHandler::Api
|
487
|
+
def process(task, sequence, step)
|
488
|
+
order_id = task.context['order_id']
|
489
|
+
connection.get("/orders/#{order_id}")
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# POST request example
|
494
|
+
class CreatePaymentHandler < Tasker::StepHandler::Api
|
495
|
+
def process(task, sequence, step)
|
496
|
+
payment_data = {
|
497
|
+
amount: task.context['amount'],
|
498
|
+
currency: task.context['currency'],
|
499
|
+
customer_id: task.context['customer_id']
|
500
|
+
}
|
501
|
+
connection.post('/payments', payment_data)
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
# PUT request with data from previous steps
|
506
|
+
class UpdateInventoryHandler < Tasker::StepHandler::Api
|
507
|
+
def process(task, sequence, step)
|
508
|
+
# Get data from previous steps
|
509
|
+
order_items = get_previous_step_data(sequence, 'fetch_order', 'items')
|
510
|
+
|
511
|
+
inventory_updates = order_items.map do |item|
|
512
|
+
{ product_id: item['product_id'], quantity: -item['quantity'] }
|
513
|
+
end
|
514
|
+
|
515
|
+
connection.put('/inventory/bulk-update', { updates: inventory_updates })
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
```
|
521
|
+
|
522
|
+
**Custom Response Processing**:
|
523
|
+
|
524
|
+
Override the `process_results` method to customize how API responses are processed and stored:
|
525
|
+
|
526
|
+
```ruby
|
527
|
+
module OrderProcess
|
528
|
+
module StepHandler
|
529
|
+
class FetchUserDataHandler < Tasker::StepHandler::Api
|
530
|
+
def process(task, sequence, step)
|
531
|
+
user_id = task.context['user_id']
|
532
|
+
connection.get("/users/#{user_id}/profile")
|
533
|
+
end
|
534
|
+
|
535
|
+
# Override to customize response processing
|
536
|
+
def process_results(step, process_output, initial_results)
|
537
|
+
# process_output is the Faraday::Response from your process method
|
538
|
+
if process_output.status == 200
|
539
|
+
data = JSON.parse(process_output.body)
|
540
|
+
step.results = {
|
541
|
+
profile: data['user_profile'],
|
542
|
+
preferences: data['preferences'],
|
543
|
+
last_login: data['last_login_at'],
|
544
|
+
api_response_time: process_output.headers['x-response-time']
|
545
|
+
}
|
546
|
+
else
|
547
|
+
# Let framework handle error - this will trigger retries if configured
|
548
|
+
raise "API error: #{process_output.status} - #{process_output.body}"
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
```
|
555
|
+
|
556
|
+
**Setting Results Directly in `process`**:
|
557
|
+
|
558
|
+
You can also set `step.results` directly in your `process` method if you prefer:
|
559
|
+
|
560
|
+
```ruby
|
561
|
+
module OrderProcess
|
562
|
+
module StepHandler
|
563
|
+
class ProcessOrderHandler < Tasker::StepHandler::Api
|
564
|
+
def process(task, sequence, step)
|
565
|
+
response = connection.post('/orders', task.context)
|
566
|
+
|
567
|
+
# Set results directly - framework will respect this
|
568
|
+
if response.status == 201
|
569
|
+
order_data = JSON.parse(response.body)
|
570
|
+
step.results = {
|
571
|
+
order_id: order_data['id'],
|
572
|
+
status: order_data['status'],
|
573
|
+
created_at: order_data['created_at']
|
574
|
+
}
|
575
|
+
end
|
576
|
+
|
577
|
+
response # Return response for framework processing
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
```
|
583
|
+
|
584
|
+
**Key API Step Handler Principles**:
|
585
|
+
|
586
|
+
1. **Implement `process` method** - This is your extension point for HTTP request logic
|
587
|
+
2. **Use the `connection` object** - Pre-configured Faraday connection with retry/timeout handling
|
588
|
+
3. **Return the response** - Let the framework handle response processing and error detection
|
589
|
+
4. **Override `process_results` for custom processing** - Transform responses while preserving framework behavior
|
590
|
+
5. **Set `step.results` for custom data** - Either in `process` or `process_results`
|
591
|
+
6. **Raise exceptions for failures** - Framework handles error states and retry orchestration
|
592
|
+
7. **Never override `handle`** - This is framework-only coordination code
|
593
|
+
|
594
|
+
### Step Handler Features
|
595
|
+
|
596
|
+
- **Automatic Result Storage**: Return values automatically stored in `step.results`
|
597
|
+
- **Context Access**: Full access to task context and previous step results
|
598
|
+
- **Error Handling**: Framework determines success/failure based on exceptions (see Error Handling Patterns in Best Practices)
|
599
|
+
- **Custom Processing**: Override `process_results` for custom result handling
|
600
|
+
- **Event Integration**: Automatic event publishing for observability
|
601
|
+
|
602
|
+
### Accessing Previous Step Data
|
603
|
+
|
604
|
+
```ruby
|
605
|
+
def process(task, sequence, step)
|
606
|
+
# Access task context
|
607
|
+
order_id = task.context['order_id']
|
608
|
+
|
609
|
+
# Find specific step by name
|
610
|
+
payment_step = sequence.find_step_by_name('process_payment')
|
611
|
+
payment_id = payment_step.results['payment_id']
|
612
|
+
|
613
|
+
# Get data from multiple steps
|
614
|
+
product_data = get_previous_step_data(sequence, 'fetch_products', 'products')
|
615
|
+
inventory_data = get_previous_step_data(sequence, 'check_inventory', 'levels')
|
616
|
+
|
617
|
+
# Your business logic here
|
618
|
+
process_order_fulfillment(order_id, payment_id, product_data, inventory_data)
|
619
|
+
end
|
620
|
+
```
|
621
|
+
|
622
|
+
## 3. Event Subscribers
|
623
|
+
|
624
|
+
Event subscribers handle **"collateral" or "secondary" logic** - operations that support observability, monitoring, and alerting but are not core business requirements. They respond to workflow events and provide operational visibility into system behavior.
|
625
|
+
|
626
|
+
### Architectural Distinction: Subscribers vs Steps
|
627
|
+
|
628
|
+
**Event Subscribers** are for collateral concerns:
|
629
|
+
- **Operational Observability**: Logging, metrics, telemetry, traces
|
630
|
+
- **Alerting & Monitoring**: Sentry errors, PagerDuty alerts, operational notifications
|
631
|
+
- **Analytics**: Business intelligence, usage tracking, performance monitoring
|
632
|
+
- **External Integrations**: Non-critical third-party service notifications
|
633
|
+
|
634
|
+
**Workflow Steps** are for business-critical operations requiring:
|
635
|
+
- **Idempotency**: Can be safely retried without side effects
|
636
|
+
- **Retryability**: Built-in retry logic with exponential backoff
|
637
|
+
- **Explicit Lifecycle Tracking**: Success/failure states that matter to the business
|
638
|
+
- **Transactional Integrity**: Operations that need to be rolled back on failure
|
639
|
+
|
640
|
+
**Rule of Thumb**: If the operation must succeed for the workflow to be considered complete, it should be a workflow step. If it's supporting infrastructure (logging, monitoring, analytics), it should be an event subscriber.
|
641
|
+
|
642
|
+
### Creating Event Subscribers
|
643
|
+
|
644
|
+
Use the generator to create subscribers with automatic method routing:
|
645
|
+
|
646
|
+
```bash
|
647
|
+
# Generate a subscriber for specific events
|
648
|
+
rails generate tasker:subscriber notification --events task.completed task.failed step.failed
|
649
|
+
|
650
|
+
# Generate a metrics collector
|
651
|
+
rails generate tasker:subscriber metrics --events task.completed step.completed task.failed step.failed
|
652
|
+
```
|
653
|
+
|
654
|
+
### Event Subscriber Structure
|
655
|
+
|
656
|
+
```ruby
|
657
|
+
# app/subscribers/observability_subscriber.rb
|
658
|
+
class ObservabilitySubscriber < Tasker::Events::Subscribers::BaseSubscriber
|
659
|
+
# Subscribe to specific events for operational monitoring
|
660
|
+
subscribe_to 'task.completed', 'task.failed', 'step.failed', 'order.fulfilled'
|
661
|
+
|
662
|
+
# Automatic method routing: task.completed -> handle_task_completed
|
663
|
+
def handle_task_completed(event)
|
664
|
+
task_id = safe_get(event, :task_id)
|
665
|
+
task_name = safe_get(event, :task_name, 'unknown')
|
666
|
+
execution_duration = safe_get(event, :execution_duration, 0)
|
667
|
+
|
668
|
+
# Operational logging and metrics (collateral concerns)
|
669
|
+
Rails.logger.info "Task completed: #{task_name} (#{task_id}) in #{execution_duration}s"
|
670
|
+
StatsD.histogram('tasker.task.duration', execution_duration, tags: ["task:#{task_name}"])
|
671
|
+
end
|
672
|
+
|
673
|
+
def handle_task_failed(event)
|
674
|
+
task_id = safe_get(event, :task_id)
|
675
|
+
error_message = safe_get(event, :error_message, 'Unknown error')
|
676
|
+
|
677
|
+
# Send alerts to operational tools (collateral concerns)
|
678
|
+
Sentry.capture_message("Task failed: #{task_id}", level: 'error', extra: { error: error_message })
|
679
|
+
PagerDutyService.trigger_alert(
|
680
|
+
summary: "Tasker workflow failed",
|
681
|
+
severity: 'error',
|
682
|
+
details: { task_id: task_id, error: error_message }
|
683
|
+
)
|
684
|
+
end
|
685
|
+
|
686
|
+
def handle_step_failed(event)
|
687
|
+
step_id = safe_get(event, :step_id)
|
688
|
+
step_name = safe_get(event, :step_name, 'unknown')
|
689
|
+
task_id = safe_get(event, :task_id)
|
690
|
+
|
691
|
+
# Operational logging for debugging (collateral concern)
|
692
|
+
Rails.logger.error "Step failure in task #{task_id}: #{step_name} (#{step_id})"
|
693
|
+
end
|
694
|
+
|
695
|
+
def handle_order_fulfilled(event)
|
696
|
+
order_id = safe_get(event, :order_id)
|
697
|
+
customer_id = safe_get(event, :customer_id)
|
698
|
+
|
699
|
+
# Analytics and monitoring (collateral concerns)
|
700
|
+
AnalyticsService.track_order_fulfillment(order_id, customer_id)
|
701
|
+
Rails.logger.info "Order fulfilled: #{order_id} for customer #{customer_id}"
|
702
|
+
end
|
703
|
+
end
|
704
|
+
```
|
705
|
+
|
706
|
+
### Available Events
|
707
|
+
|
708
|
+
Discover all available events using the event catalog:
|
709
|
+
|
710
|
+
```ruby
|
711
|
+
# In Rails console or your code
|
712
|
+
Tasker::Events.catalog.keys
|
713
|
+
# => ["task.started", "task.completed", "task.failed", "step.started", ...]
|
714
|
+
|
715
|
+
# Get detailed event information
|
716
|
+
Tasker::Events.event_info('task.completed')
|
717
|
+
# => { name: "task.completed", category: "task", description: "...", ... }
|
718
|
+
|
719
|
+
# Browse by category
|
720
|
+
Tasker::Events.task_events.keys # Task lifecycle events
|
721
|
+
Tasker::Events.step_events.keys # Step execution events
|
722
|
+
Tasker::Events.workflow_events.keys # Orchestration events
|
723
|
+
```
|
724
|
+
|
725
|
+
### Real-World Integration Examples
|
726
|
+
|
727
|
+
**Metrics Collection (DataDog)**:
|
728
|
+
```ruby
|
729
|
+
class MetricsSubscriber < Tasker::Events::Subscribers::BaseSubscriber
|
730
|
+
subscribe_to 'task.completed', 'task.failed', 'step.completed'
|
731
|
+
|
732
|
+
def handle_task_completed(event)
|
733
|
+
task_name = safe_get(event, :task_name, 'unknown')
|
734
|
+
execution_duration = safe_get(event, :execution_duration, 0)
|
735
|
+
|
736
|
+
StatsD.histogram('tasker.task.duration', execution_duration, tags: ["task:#{task_name}"])
|
737
|
+
StatsD.increment('tasker.task.completed', tags: ["task:#{task_name}"])
|
738
|
+
end
|
739
|
+
|
740
|
+
def handle_task_failed(event)
|
741
|
+
task_name = safe_get(event, :task_name, 'unknown')
|
742
|
+
error_class = safe_get(event, :exception_class, 'unknown')
|
743
|
+
|
744
|
+
StatsD.increment('tasker.task.failed', tags: ["task:#{task_name}", "error:#{error_class}"])
|
745
|
+
end
|
746
|
+
end
|
747
|
+
```
|
748
|
+
|
749
|
+
**Error Tracking (Sentry)**:
|
750
|
+
```ruby
|
751
|
+
class SentrySubscriber < Tasker::Events::Subscribers::BaseSubscriber
|
752
|
+
subscribe_to 'task.failed', 'step.failed'
|
753
|
+
|
754
|
+
def handle_task_failed(event)
|
755
|
+
task_id = safe_get(event, :task_id)
|
756
|
+
error_message = safe_get(event, :error_message, 'Unknown error')
|
757
|
+
|
758
|
+
Sentry.capture_message(error_message,
|
759
|
+
level: 'error',
|
760
|
+
fingerprint: ['tasker', 'task_failed', task_id],
|
761
|
+
tags: { task_id: task_id, component: 'tasker' }
|
762
|
+
)
|
763
|
+
end
|
764
|
+
end
|
765
|
+
```
|
766
|
+
|
767
|
+
## 4. YAML Configuration
|
768
|
+
|
769
|
+
YAML files provide declarative configuration for task handlers, defining workflows, dependencies, and step settings without requiring code changes.
|
770
|
+
|
771
|
+
### Task Handler YAML Structure
|
772
|
+
|
773
|
+
```yaml
|
774
|
+
# config/tasker/tasks/payments/order_process.yaml
|
775
|
+
---
|
776
|
+
name: order_process
|
777
|
+
namespace_name: payments # NEW: TaskNamespace organization
|
778
|
+
version: 1.2.0 # NEW: Semantic versioning
|
779
|
+
module_namespace: OrderProcess # Ruby module namespace
|
780
|
+
task_handler_class: OrderHandler
|
781
|
+
|
782
|
+
# JSON Schema validation for task context
|
783
|
+
schema:
|
784
|
+
type: object
|
785
|
+
required:
|
786
|
+
- order_id
|
787
|
+
- customer_id
|
788
|
+
properties:
|
789
|
+
order_id:
|
790
|
+
type: integer
|
791
|
+
customer_id:
|
792
|
+
type: integer
|
793
|
+
priority:
|
794
|
+
type: string
|
795
|
+
enum: [low, normal, high, critical]
|
796
|
+
|
797
|
+
# Step definitions with dependencies
|
798
|
+
step_templates:
|
799
|
+
- name: fetch_order
|
800
|
+
description: Retrieve order details from database
|
801
|
+
handler_class: OrderProcess::StepHandler::FetchOrderHandler
|
802
|
+
|
803
|
+
- name: validate_inventory
|
804
|
+
description: Check product availability
|
805
|
+
depends_on_step: fetch_order
|
806
|
+
handler_class: OrderProcess::StepHandler::ValidateInventoryHandler
|
807
|
+
default_retryable: true
|
808
|
+
default_retry_limit: 3
|
809
|
+
|
810
|
+
- name: process_payment
|
811
|
+
description: Charge customer payment method
|
812
|
+
depends_on_step: validate_inventory
|
813
|
+
handler_class: OrderProcess::StepHandler::ProcessPaymentHandler
|
814
|
+
default_retryable: true
|
815
|
+
default_retry_limit: 2
|
816
|
+
|
817
|
+
- name: update_inventory
|
818
|
+
description: Decrement inventory levels
|
819
|
+
depends_on_step: process_payment
|
820
|
+
handler_class: OrderProcess::StepHandler::UpdateInventoryHandler
|
821
|
+
|
822
|
+
- name: send_confirmation
|
823
|
+
description: Send order confirmation email
|
824
|
+
depends_on_step: update_inventory
|
825
|
+
handler_class: OrderProcess::StepHandler::SendConfirmationHandler
|
826
|
+
default_retryable: true
|
827
|
+
default_retry_limit: 5
|
828
|
+
|
829
|
+
# Environment-specific overrides
|
830
|
+
environments:
|
831
|
+
development:
|
832
|
+
step_templates:
|
833
|
+
- name: process_payment
|
834
|
+
# Use test payment processor in development
|
835
|
+
handler_config:
|
836
|
+
payment_processor: test
|
837
|
+
|
838
|
+
production:
|
839
|
+
step_templates:
|
840
|
+
- name: process_payment
|
841
|
+
# Production payment configuration
|
842
|
+
handler_config:
|
843
|
+
payment_processor: stripe
|
844
|
+
timeout: 30
|
845
|
+
```
|
846
|
+
|
847
|
+
### Advanced YAML Features
|
848
|
+
|
849
|
+
**Multiple Dependencies**:
|
850
|
+
```yaml
|
851
|
+
- name: finalize_order
|
852
|
+
description: Complete order processing
|
853
|
+
depends_on_steps: # Multiple dependencies
|
854
|
+
- process_payment
|
855
|
+
- update_inventory
|
856
|
+
- reserve_shipping
|
857
|
+
handler_class: OrderProcess::StepHandler::FinalizeOrderHandler
|
858
|
+
```
|
859
|
+
|
860
|
+
**Environment-Specific Configuration**:
|
861
|
+
```yaml
|
862
|
+
environments:
|
863
|
+
test:
|
864
|
+
step_templates:
|
865
|
+
- name: send_email
|
866
|
+
handler_config:
|
867
|
+
email_service: mock
|
868
|
+
|
869
|
+
staging:
|
870
|
+
step_templates:
|
871
|
+
- name: send_email
|
872
|
+
handler_config:
|
873
|
+
email_service: sendgrid_test
|
874
|
+
|
875
|
+
production:
|
876
|
+
step_templates:
|
877
|
+
- name: send_email
|
878
|
+
handler_config:
|
879
|
+
email_service: sendgrid_production
|
880
|
+
api_key: ${SENDGRID_API_KEY}
|
881
|
+
```
|
882
|
+
|
883
|
+
**API Step Configuration**:
|
884
|
+
```yaml
|
885
|
+
- name: fetch_external_data
|
886
|
+
handler_class: OrderProcess::StepHandler::FetchExternalDataHandler
|
887
|
+
handler_config:
|
888
|
+
type: api
|
889
|
+
url: https://api.external-service.com/data
|
890
|
+
method: GET
|
891
|
+
headers:
|
892
|
+
Authorization: "Bearer ${API_TOKEN}"
|
893
|
+
timeout: 15
|
894
|
+
retries: 3
|
895
|
+
```
|
896
|
+
|
897
|
+
### TaskNamespace + Versioning in YAML
|
898
|
+
|
899
|
+
The new namespace and versioning system provides enterprise-scale organization:
|
900
|
+
|
901
|
+
#### Namespace-Based File Organization
|
902
|
+
|
903
|
+
**Recommended File Structure**:
|
904
|
+
```
|
905
|
+
config/tasker/tasks/
|
906
|
+
├── payments/
|
907
|
+
│ ├── process_order.yaml # version: 1.0.0
|
908
|
+
│ ├── process_refund.yaml # version: 1.1.0
|
909
|
+
│ └── validate_payment.yaml # version: 2.0.0
|
910
|
+
├── inventory/
|
911
|
+
│ ├── process_order.yaml # version: 1.5.0 (same name, different namespace)
|
912
|
+
│ ├── stock_check.yaml # version: 1.0.0
|
913
|
+
│ └── reorder_items.yaml # version: 1.2.0
|
914
|
+
└── notifications/
|
915
|
+
├── send_email.yaml # version: 1.0.0
|
916
|
+
├── send_sms.yaml # version: 1.1.0
|
917
|
+
└── push_notification.yaml # version: 2.0.0
|
918
|
+
```
|
919
|
+
|
920
|
+
#### Version Coexistence Examples
|
921
|
+
|
922
|
+
**Multiple Versions of Same Task**:
|
923
|
+
```yaml
|
924
|
+
# config/tasker/tasks/payments/process_order_v1.yaml
|
925
|
+
---
|
926
|
+
name: process_order
|
927
|
+
namespace_name: payments
|
928
|
+
version: 1.0.0 # Legacy version
|
929
|
+
task_handler_class: Payments::ProcessOrderV1
|
930
|
+
|
931
|
+
# config/tasker/tasks/payments/process_order_v2.yaml
|
932
|
+
---
|
933
|
+
name: process_order
|
934
|
+
namespace_name: payments
|
935
|
+
version: 2.0.0 # Current version
|
936
|
+
task_handler_class: Payments::ProcessOrderV2
|
937
|
+
```
|
938
|
+
|
939
|
+
#### Namespace-Specific Configuration
|
940
|
+
|
941
|
+
**Domain-Specific Settings**:
|
942
|
+
```yaml
|
943
|
+
# config/tasker/tasks/integrations/api_sync.yaml
|
944
|
+
---
|
945
|
+
name: api_sync
|
946
|
+
namespace_name: integrations
|
947
|
+
version: 1.3.0
|
948
|
+
task_handler_class: Integrations::ApiSyncHandler
|
949
|
+
|
950
|
+
# Integration-specific settings
|
951
|
+
handler_config:
|
952
|
+
max_concurrent_requests: 5
|
953
|
+
rate_limit_per_second: 10
|
954
|
+
dependent_systems: # External systems this task interacts with
|
955
|
+
- salesforce_api
|
956
|
+
- inventory_service
|
957
|
+
- notification_queue
|
958
|
+
|
959
|
+
step_templates:
|
960
|
+
- name: fetch_data
|
961
|
+
handler_class: Integrations::FetchDataHandler
|
962
|
+
dependent_system: salesforce_api # Step-level system identification
|
963
|
+
|
964
|
+
- name: transform_data
|
965
|
+
handler_class: Integrations::TransformDataHandler
|
966
|
+
|
967
|
+
- name: upload_data
|
968
|
+
handler_class: Integrations::UploadDataHandler
|
969
|
+
dependent_system: inventory_service
|
970
|
+
```
|
971
|
+
|
972
|
+
#### Configuration Validation & Defaults
|
973
|
+
|
974
|
+
**YAML Configuration Schema**:
|
975
|
+
```yaml
|
976
|
+
# Full configuration with all optional fields
|
977
|
+
---
|
978
|
+
name: process_payment # Required
|
979
|
+
namespace_name: payments # Optional - defaults to 'default'
|
980
|
+
version: 2.1.0 # Optional - defaults to '0.1.0'
|
981
|
+
module_namespace: Payments # Optional - Ruby module namespace
|
982
|
+
task_handler_class: ProcessPaymentHandler # Required
|
983
|
+
description: "Advanced payment processing" # Optional - for documentation
|
984
|
+
|
985
|
+
# Database configuration (optional)
|
986
|
+
configuration:
|
987
|
+
priority: high
|
988
|
+
max_execution_time: 300
|
989
|
+
custom_metadata:
|
990
|
+
team: payments
|
991
|
+
owner: alice@company.com
|
992
|
+
documentation_url: https://wiki.company.com/payments
|
993
|
+
```
|
994
|
+
|
995
|
+
#### Migration Strategy for Existing Configurations
|
996
|
+
|
997
|
+
**Backward Compatibility**: Existing YAML files continue working unchanged:
|
998
|
+
```yaml
|
999
|
+
# Existing file - continues working with defaults
|
1000
|
+
---
|
1001
|
+
name: legacy_task
|
1002
|
+
task_handler_class: LegacyHandler
|
1003
|
+
# Automatically gets: namespace_name: 'default', version: '0.1.0'
|
1004
|
+
|
1005
|
+
# Enhanced file - new capabilities
|
1006
|
+
---
|
1007
|
+
name: legacy_task
|
1008
|
+
namespace_name: default # Explicit default
|
1009
|
+
version: 0.1.0 # Explicit version
|
1010
|
+
task_handler_class: LegacyHandler
|
1011
|
+
```
|
1012
|
+
|
1013
|
+
## 5. Authentication & Authorization
|
1014
|
+
|
1015
|
+
Tasker provides a comprehensive, production-ready authentication and authorization system that works with any Rails authentication solution. The system uses **dependency injection** and **resource-based authorization** to provide enterprise-grade security for both REST APIs and GraphQL endpoints.
|
1016
|
+
|
1017
|
+
### Key Features
|
1018
|
+
|
1019
|
+
- **Provider Agnostic**: Works with Devise, JWT, OmniAuth, custom authentication, or no authentication
|
1020
|
+
- **Resource-Based Authorization**: Granular permissions using resource:action patterns (e.g., `tasker.task:create`)
|
1021
|
+
- **GraphQL Operation-Level Authorization**: Revolutionary security for GraphQL that maps operations to resource permissions
|
1022
|
+
- **Automatic Controller Integration**: Authentication and authorization work seamlessly across REST and GraphQL
|
1023
|
+
- **Comprehensive Generators**: Create production-ready authenticators and authorization coordinators
|
1024
|
+
- **Zero Breaking Changes**: All features are opt-in and backward compatible
|
1025
|
+
|
1026
|
+
### Quick Configuration Example
|
1027
|
+
|
1028
|
+
```ruby
|
1029
|
+
# config/initializers/tasker.rb
|
1030
|
+
Tasker.configuration do |config|
|
1031
|
+
config.auth do |auth|
|
1032
|
+
# Authentication
|
1033
|
+
auth.authentication_enabled = true
|
1034
|
+
auth.authenticator_class = 'YourCustomAuthenticator'
|
1035
|
+
|
1036
|
+
# Authorization
|
1037
|
+
auth.authorization_enabled = true
|
1038
|
+
auth.authorization_coordinator_class = 'YourAuthorizationCoordinator'
|
1039
|
+
auth.user_class = 'User'
|
1040
|
+
end
|
1041
|
+
end
|
1042
|
+
```
|
1043
|
+
|
1044
|
+
### Available Generators
|
1045
|
+
|
1046
|
+
```bash
|
1047
|
+
# Generate authenticators for different systems
|
1048
|
+
rails generate tasker:authenticator CompanyJWT --type=jwt
|
1049
|
+
rails generate tasker:authenticator AdminAuth --type=devise --user-class=Admin
|
1050
|
+
rails generate tasker:authenticator ApiAuth --type=api_token
|
1051
|
+
rails generate tasker:authenticator SocialAuth --type=omniauth
|
1052
|
+
|
1053
|
+
# Generate authorization coordinator
|
1054
|
+
rails generate tasker:authorization_coordinator CompanyAuth
|
1055
|
+
```
|
1056
|
+
|
1057
|
+
### GraphQL Authorization Example
|
1058
|
+
|
1059
|
+
The system automatically maps GraphQL operations to resource permissions:
|
1060
|
+
|
1061
|
+
```ruby
|
1062
|
+
# This GraphQL query:
|
1063
|
+
query { tasks { taskId status } }
|
1064
|
+
|
1065
|
+
# Automatically requires: tasker.task:index permission
|
1066
|
+
|
1067
|
+
# This mutation:
|
1068
|
+
mutation { createTask(input: {...}) { taskId } }
|
1069
|
+
|
1070
|
+
# Automatically requires: tasker.task:create permission
|
1071
|
+
```
|
1072
|
+
|
1073
|
+
### Resource-Based Permissions
|
1074
|
+
|
1075
|
+
Authorization uses a simple resource:action permission model:
|
1076
|
+
|
1077
|
+
```ruby
|
1078
|
+
# Available permissions:
|
1079
|
+
'tasker.task:index' # List all tasks
|
1080
|
+
'tasker.task:create' # Create new tasks
|
1081
|
+
'tasker.workflow_step:show' # View individual workflow steps
|
1082
|
+
'tasker.task_diagram:index' # List task diagrams
|
1083
|
+
```
|
1084
|
+
|
1085
|
+
### Complete Documentation
|
1086
|
+
|
1087
|
+
For comprehensive documentation including:
|
1088
|
+
- **Quick Start Guides** - Get authentication working in minutes
|
1089
|
+
- **Custom Authenticator Examples** - JWT, Devise, API tokens, and more
|
1090
|
+
- **Authorization Coordinator Patterns** - Role-based, context-aware, and time-based authorization
|
1091
|
+
- **GraphQL Authorization Details** - Operation mapping and context handling
|
1092
|
+
- **Production Best Practices** - Security, performance, and monitoring guidelines
|
1093
|
+
- **Testing Strategies** - Complete test examples and isolation techniques
|
1094
|
+
|
1095
|
+
**See [Authentication & Authorization Guide](AUTH.md)** for complete documentation.
|
1096
|
+
|
1097
|
+
## 6. Multi-Database Support
|
1098
|
+
|
1099
|
+
Tasker provides optional multi-database support using Rails' standard multi-database conventions. This allows Tasker models to use a separate database from the host application for data isolation, performance, or compliance requirements.
|
1100
|
+
|
1101
|
+
### Key Features
|
1102
|
+
|
1103
|
+
- **Rails Multi-Database Integration**: Uses Rails' `connects_to` API following official conventions
|
1104
|
+
- **Standard Configuration**: Leverages Rails database.yml patterns with named databases
|
1105
|
+
- **Automatic Model Support**: All Tasker models inherit multi-database capability automatically
|
1106
|
+
- **Zero Breaking Changes**: Fully backward compatible with existing installations
|
1107
|
+
- **Environment-Specific**: Supports different database configurations per environment
|
1108
|
+
|
1109
|
+
### Configuration
|
1110
|
+
|
1111
|
+
```ruby
|
1112
|
+
# config/initializers/tasker.rb
|
1113
|
+
|
1114
|
+
# Default: Use host application database (shared)
|
1115
|
+
Tasker.configuration do |config|
|
1116
|
+
config.database.enable_secondary_database = false
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
# Use dedicated Tasker database
|
1120
|
+
Tasker.configuration do |config|
|
1121
|
+
config.database.enable_secondary_database = true
|
1122
|
+
config.database.name = :tasker
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
# Environment-specific configuration
|
1126
|
+
Tasker.configuration do |config|
|
1127
|
+
config.database.enable_secondary_database = Rails.env.production?
|
1128
|
+
config.database.name = Rails.env.production? ? :tasker : nil
|
1129
|
+
end
|
1130
|
+
```
|
1131
|
+
|
1132
|
+
### Database Configuration
|
1133
|
+
|
1134
|
+
```yaml
|
1135
|
+
# config/database.yml
|
1136
|
+
production:
|
1137
|
+
primary:
|
1138
|
+
database: my_primary_database
|
1139
|
+
adapter: postgresql
|
1140
|
+
username: app_user
|
1141
|
+
password: <%= ENV['DATABASE_PASSWORD'] %>
|
1142
|
+
|
1143
|
+
tasker:
|
1144
|
+
database: my_tasker_database
|
1145
|
+
adapter: postgresql
|
1146
|
+
username: tasker_user
|
1147
|
+
password: <%= ENV['TASKER_DATABASE_PASSWORD'] %>
|
1148
|
+
```
|
1149
|
+
|
1150
|
+
### Benefits of Multi-Database Setup
|
1151
|
+
|
1152
|
+
**Data Isolation**: Separate Tasker data from application data for security or compliance
|
1153
|
+
|
1154
|
+
**Performance**: Dedicated database resources for workflow processing
|
1155
|
+
|
1156
|
+
**Scaling**: Independent scaling of workflow database based on usage patterns
|
1157
|
+
|
1158
|
+
**Backup Strategy**: Separate backup and recovery policies for workflow data
|
1159
|
+
|
1160
|
+
**Development**: Easier testing and development with isolated workflow data
|
1161
|
+
|
1162
|
+
### Migration Support
|
1163
|
+
|
1164
|
+
When using a secondary database, Tasker migrations automatically target the correct database:
|
1165
|
+
|
1166
|
+
```bash
|
1167
|
+
# Migrations run against the configured Tasker database
|
1168
|
+
bundle exec rails tasker:install:migrations
|
1169
|
+
bundle exec rails tasker:install:database_objects
|
1170
|
+
bundle exec rails db:migrate
|
1171
|
+
```
|
1172
|
+
|
1173
|
+
### Production Considerations
|
1174
|
+
|
1175
|
+
- **Connection Pooling**: Configure appropriate connection pool sizes for both databases
|
1176
|
+
- **Monitoring**: Monitor connection usage and performance for both databases
|
1177
|
+
- **Backup Strategy**: Implement coordinated backup strategies if data consistency across databases is required
|
1178
|
+
- **Network Latency**: Consider network latency if databases are on different servers
|
1179
|
+
|
1180
|
+
## 7. Dependency Graph & Bottleneck Analysis Configuration
|
1181
|
+
|
1182
|
+
Tasker provides advanced dependency graph analysis and bottleneck detection capabilities that can be fine-tuned for your specific workflow patterns. The dependency graph configuration controls how Tasker analyzes workflow dependencies, identifies bottlenecks, and calculates impact scores for optimization recommendations.
|
1183
|
+
|
1184
|
+
### Key Features
|
1185
|
+
|
1186
|
+
- **Configurable Impact Scoring**: Customize how different factors influence bottleneck calculations
|
1187
|
+
- **Adaptive Severity Classification**: Define custom thresholds for Critical/High/Medium/Low priority bottlenecks
|
1188
|
+
- **Dynamic Duration Estimation**: Configure time estimates for path analysis and planning
|
1189
|
+
- **State-Based Multipliers**: Adjust scoring based on step states and execution conditions
|
1190
|
+
- **Retry Pattern Analysis**: Configure penalties for instability and failure patterns
|
1191
|
+
|
1192
|
+
### Quick Configuration Example
|
1193
|
+
|
1194
|
+
```ruby
|
1195
|
+
# config/initializers/tasker.rb
|
1196
|
+
Tasker.configuration do |config|
|
1197
|
+
config.dependency_graph do |graph|
|
1198
|
+
# Prioritize blocked steps more heavily
|
1199
|
+
graph.impact_multipliers = {
|
1200
|
+
blocked_weight: 20, # Increase from default 15
|
1201
|
+
error_penalty: 40 # Increase from default 30
|
1202
|
+
}
|
1203
|
+
|
1204
|
+
# Lower thresholds for faster bottleneck detection
|
1205
|
+
graph.severity_thresholds = {
|
1206
|
+
critical: 80, # Decrease from default 100
|
1207
|
+
high: 40, # Decrease from default 50
|
1208
|
+
medium: 15 # Decrease from default 20
|
1209
|
+
}
|
1210
|
+
end
|
1211
|
+
end
|
1212
|
+
```
|
1213
|
+
|
1214
|
+
### Configuration Options
|
1215
|
+
|
1216
|
+
#### Impact Multipliers
|
1217
|
+
|
1218
|
+
Control how different factors contribute to the overall bottleneck impact score:
|
1219
|
+
|
1220
|
+
```ruby
|
1221
|
+
config.dependency_graph do |graph|
|
1222
|
+
graph.impact_multipliers = {
|
1223
|
+
downstream_weight: 5, # Weight for downstream step count
|
1224
|
+
blocked_weight: 15, # Weight for blocked step count (most critical)
|
1225
|
+
path_length_weight: 10, # Weight for critical path length
|
1226
|
+
completed_penalty: 15, # Penalty to reduce priority of completed work
|
1227
|
+
blocked_penalty: 25, # Penalty to increase priority of blocked work
|
1228
|
+
error_penalty: 30, # Penalty for steps in error state (highest)
|
1229
|
+
retry_penalty: 10 # Penalty for steps requiring retries
|
1230
|
+
}
|
1231
|
+
end
|
1232
|
+
```
|
1233
|
+
|
1234
|
+
**Impact Score Calculation:**
|
1235
|
+
```
|
1236
|
+
base_score = (downstream_count * downstream_weight) + (blocked_count * blocked_weight)
|
1237
|
+
path_score = path_length * path_length_weight
|
1238
|
+
penalties = (completed_steps * completed_penalty) + (blocked_steps * blocked_penalty) +
|
1239
|
+
(error_steps * error_penalty) + (retry_steps * retry_penalty)
|
1240
|
+
final_score = (base_score + path_score + penalties) * severity_multiplier
|
1241
|
+
```
|
1242
|
+
|
1243
|
+
#### Severity Multipliers
|
1244
|
+
|
1245
|
+
Adjust impact scores based on step states and execution conditions:
|
1246
|
+
|
1247
|
+
```ruby
|
1248
|
+
config.dependency_graph do |graph|
|
1249
|
+
graph.severity_multipliers = {
|
1250
|
+
error_state: 2.0, # Multiply score by 2.0 for steps in error state
|
1251
|
+
exhausted_retry_bonus: 0.5, # Additional 0.5x multiplier for exhausted retries
|
1252
|
+
dependency_issue: 1.2 # 1.2x multiplier for dependency-related issues
|
1253
|
+
}
|
1254
|
+
end
|
1255
|
+
```
|
1256
|
+
|
1257
|
+
**State-Based Scoring Examples:**
|
1258
|
+
- Normal step: `base_score * 1.0`
|
1259
|
+
- Error step: `base_score * 2.0`
|
1260
|
+
- Error step with exhausted retries: `base_score * (2.0 + 0.5) = base_score * 2.5`
|
1261
|
+
- Dependency blocked step: `base_score * 1.2`
|
1262
|
+
|
1263
|
+
#### Penalty Constants
|
1264
|
+
|
1265
|
+
Add fixed penalty points for specific problematic conditions:
|
1266
|
+
|
1267
|
+
```ruby
|
1268
|
+
config.dependency_graph do |graph|
|
1269
|
+
graph.penalty_constants = {
|
1270
|
+
retry_instability: 3, # +3 points per retry attempt
|
1271
|
+
non_retryable: 10, # +10 points for non-retryable failures
|
1272
|
+
exhausted_retry: 20 # +20 points for exhausted retry attempts
|
1273
|
+
}
|
1274
|
+
end
|
1275
|
+
```
|
1276
|
+
|
1277
|
+
**Penalty Application:**
|
1278
|
+
- Step with 2 retry attempts: `+6 penalty points`
|
1279
|
+
- Non-retryable failure: `+10 penalty points`
|
1280
|
+
- Exhausted retries (3+ attempts): `+20 penalty points`
|
1281
|
+
|
1282
|
+
#### Severity Thresholds
|
1283
|
+
|
1284
|
+
Define score ranges for bottleneck classification:
|
1285
|
+
|
1286
|
+
```ruby
|
1287
|
+
config.dependency_graph do |graph|
|
1288
|
+
graph.severity_thresholds = {
|
1289
|
+
critical: 100, # Score >= 100: Requires immediate attention
|
1290
|
+
high: 50, # Score >= 50: High priority for optimization
|
1291
|
+
medium: 20 # Score >= 20: Monitor and plan improvements
|
1292
|
+
} # Score < 20: Low priority
|
1293
|
+
end
|
1294
|
+
```
|
1295
|
+
|
1296
|
+
**Classification Examples:**
|
1297
|
+
- Score 150: **Critical** - Blocking multiple workflows, immediate intervention required
|
1298
|
+
- Score 75: **High** - Significant impact, should be addressed in current sprint
|
1299
|
+
- Score 35: **Medium** - Moderate impact, address in upcoming planning cycle
|
1300
|
+
- Score 10: **Low** - Minor impact, monitor for trends
|
1301
|
+
|
1302
|
+
#### Duration Estimates
|
1303
|
+
|
1304
|
+
Configure time estimates for path analysis and planning:
|
1305
|
+
|
1306
|
+
```ruby
|
1307
|
+
config.dependency_graph do |graph|
|
1308
|
+
graph.duration_estimates = {
|
1309
|
+
base_step_seconds: 30, # Default time estimate per step
|
1310
|
+
error_penalty_seconds: 60, # Additional time for error recovery
|
1311
|
+
retry_penalty_seconds: 30 # Additional time per retry attempt
|
1312
|
+
}
|
1313
|
+
end
|
1314
|
+
```
|
1315
|
+
|
1316
|
+
**Duration Calculation:**
|
1317
|
+
```
|
1318
|
+
estimated_duration = (step_count * base_step_seconds) +
|
1319
|
+
(error_steps * error_penalty_seconds) +
|
1320
|
+
(total_retry_attempts * retry_penalty_seconds)
|
1321
|
+
```
|
1322
|
+
|
1323
|
+
### Use Cases & Recommendations
|
1324
|
+
|
1325
|
+
#### High-Volume Transaction Processing
|
1326
|
+
|
1327
|
+
For workflows processing thousands of transactions per hour:
|
1328
|
+
|
1329
|
+
```ruby
|
1330
|
+
config.dependency_graph do |graph|
|
1331
|
+
# Emphasize blocking issues more heavily
|
1332
|
+
graph.impact_multipliers = {
|
1333
|
+
blocked_weight: 25, # Increase sensitivity to blocked steps
|
1334
|
+
error_penalty: 40 # Prioritize error resolution
|
1335
|
+
}
|
1336
|
+
|
1337
|
+
# Lower thresholds for faster detection
|
1338
|
+
graph.severity_thresholds = {
|
1339
|
+
critical: 75, # Faster escalation
|
1340
|
+
high: 35,
|
1341
|
+
medium: 15
|
1342
|
+
}
|
1343
|
+
|
1344
|
+
# Faster execution estimates for high-volume patterns
|
1345
|
+
graph.duration_estimates = {
|
1346
|
+
base_step_seconds: 15, # Optimized processes run faster
|
1347
|
+
error_penalty_seconds: 45 # Reduced error recovery time
|
1348
|
+
}
|
1349
|
+
end
|
1350
|
+
```
|
1351
|
+
|
1352
|
+
#### Long-Running Batch Processes
|
1353
|
+
|
1354
|
+
For workflows that run for hours or days:
|
1355
|
+
|
1356
|
+
```ruby
|
1357
|
+
config.dependency_graph do |graph|
|
1358
|
+
# Focus on path length and completion tracking
|
1359
|
+
graph.impact_multipliers = {
|
1360
|
+
path_length_weight: 20, # Longer paths have higher impact
|
1361
|
+
completed_penalty: 5 # Reduce penalty for completed work
|
1362
|
+
}
|
1363
|
+
|
1364
|
+
# Higher thresholds due to expected longer execution
|
1365
|
+
graph.severity_thresholds = {
|
1366
|
+
critical: 200,
|
1367
|
+
high: 100,
|
1368
|
+
medium: 50
|
1369
|
+
}
|
1370
|
+
|
1371
|
+
# Longer execution estimates
|
1372
|
+
graph.duration_estimates = {
|
1373
|
+
base_step_seconds: 120, # Steps take longer in batch processes
|
1374
|
+
error_penalty_seconds: 300 # Error recovery is more expensive
|
1375
|
+
}
|
1376
|
+
end
|
1377
|
+
```
|
1378
|
+
|
1379
|
+
#### Real-Time Processing Systems
|
1380
|
+
|
1381
|
+
For workflows requiring sub-second response times:
|
1382
|
+
|
1383
|
+
```ruby
|
1384
|
+
config.dependency_graph do |graph|
|
1385
|
+
# Maximize sensitivity to any delays
|
1386
|
+
graph.impact_multipliers = {
|
1387
|
+
retry_penalty: 20, # Retries are very costly
|
1388
|
+
error_penalty: 50 # Errors must be resolved immediately
|
1389
|
+
}
|
1390
|
+
|
1391
|
+
# Very low thresholds for immediate response
|
1392
|
+
graph.severity_thresholds = {
|
1393
|
+
critical: 30,
|
1394
|
+
high: 15,
|
1395
|
+
medium: 5
|
1396
|
+
}
|
1397
|
+
|
1398
|
+
# Tight time estimates
|
1399
|
+
graph.duration_estimates = {
|
1400
|
+
base_step_seconds: 1, # Sub-second execution expected
|
1401
|
+
error_penalty_seconds: 10, # Errors add significant delay
|
1402
|
+
retry_penalty_seconds: 5 # Each retry is expensive
|
1403
|
+
}
|
1404
|
+
end
|
1405
|
+
```
|
1406
|
+
|
1407
|
+
### Monitoring & Observability
|
1408
|
+
|
1409
|
+
The dependency graph analysis integrates with Tasker's observability system to provide actionable insights:
|
1410
|
+
|
1411
|
+
```ruby
|
1412
|
+
# Published events include bottleneck severity levels
|
1413
|
+
Tasker::Events.subscribe('task.bottleneck_detected') do |event|
|
1414
|
+
severity = event.payload['severity'] # 'Critical', 'High', 'Medium', 'Low'
|
1415
|
+
impact_score = event.payload['impact_score']
|
1416
|
+
|
1417
|
+
case severity
|
1418
|
+
when 'Critical'
|
1419
|
+
PagerDuty.alert("Critical workflow bottleneck detected: #{impact_score}")
|
1420
|
+
when 'High'
|
1421
|
+
Slack.notify("#ops", "High priority bottleneck: #{impact_score}")
|
1422
|
+
end
|
1423
|
+
end
|
1424
|
+
```
|
1425
|
+
|
1426
|
+
### Production Best Practices
|
1427
|
+
|
1428
|
+
1. **Start with Defaults**: Begin with default values and adjust based on observed patterns
|
1429
|
+
2. **Monitor Thresholds**: Track how often each severity level is triggered
|
1430
|
+
3. **A/B Testing**: Test configuration changes on a subset of workflows first
|
1431
|
+
4. **Gradual Tuning**: Make small adjustments and measure impact over time
|
1432
|
+
5. **Documentation**: Document your configuration rationale for team knowledge
|
1433
|
+
|
1434
|
+
The dependency graph configuration provides powerful tools for optimizing workflow performance while maintaining the flexibility to adapt to your specific operational requirements.
|
1435
|
+
|
1436
|
+
## 8. Cache Strategy & Custom Store Capabilities
|
1437
|
+
|
1438
|
+
Tasker includes a **Hybrid Cache Detection System** that provides intelligent cache strategy selection and supports both built-in Rails cache stores and custom cache implementations. This system automatically adapts coordination strategies based on cache store capabilities, ensuring optimal performance across different deployment environments.
|
1439
|
+
|
1440
|
+
### Key Features
|
1441
|
+
|
1442
|
+
- **🎯 Developer-Friendly API**: Declarative capability system for custom cache stores
|
1443
|
+
- **🚀 Performance Optimized**: Frozen constants provide O(1) lookup for built-in stores
|
1444
|
+
- **🔄 Hybrid Detection**: Priority-based detection system (declared → constants → custom → runtime)
|
1445
|
+
- **📊 Production-Ready**: Comprehensive structured logging and error handling
|
1446
|
+
- **⚡ Rails Integration**: Always uses `Rails.cache` as the source of truth
|
1447
|
+
|
1448
|
+
### Quick Example
|
1449
|
+
|
1450
|
+
```ruby
|
1451
|
+
# For custom cache stores - declare capabilities explicitly
|
1452
|
+
class MyAwesomeCacheStore < ActiveSupport::Cache::Store
|
1453
|
+
include Tasker::CacheCapabilities
|
1454
|
+
|
1455
|
+
# Use convenience methods for common capabilities
|
1456
|
+
supports_distributed_caching!
|
1457
|
+
supports_atomic_increment!
|
1458
|
+
supports_locking!
|
1459
|
+
|
1460
|
+
# Or declare specific capabilities
|
1461
|
+
declare_cache_capability(:advanced_analytics, true)
|
1462
|
+
declare_cache_capability(:compression_support, false)
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
# Configure Rails to use your custom store
|
1466
|
+
Rails.application.configure do
|
1467
|
+
config.cache_store = MyAwesomeCacheStore.new
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
# Tasker automatically detects capabilities and selects optimal strategy
|
1471
|
+
strategy = Tasker::CacheStrategy.detect
|
1472
|
+
puts strategy.coordination_mode # => :distributed_atomic
|
1473
|
+
puts strategy.supports?(:distributed) # => true
|
1474
|
+
```
|
1475
|
+
|
1476
|
+
### Architecture Overview
|
1477
|
+
|
1478
|
+
The cache detection system uses a **4-level priority hierarchy**:
|
1479
|
+
|
1480
|
+
```mermaid
|
1481
|
+
flowchart TD
|
1482
|
+
A[Cache Store Detection] --> B{Priority 1: Declared?}
|
1483
|
+
B -->|Yes| C[Use Declared Capabilities]
|
1484
|
+
B -->|No| D{Priority 2: Built-in Store?}
|
1485
|
+
D -->|Yes| E[Use Frozen Constants]
|
1486
|
+
D -->|No| F{Priority 3: Custom Detector?}
|
1487
|
+
F -->|Yes| G[Apply Custom Detector]
|
1488
|
+
F -->|No| H[Priority 4: Runtime Detection]
|
1489
|
+
|
1490
|
+
C --> I[Select Coordination Strategy]
|
1491
|
+
E --> I
|
1492
|
+
G --> I
|
1493
|
+
H --> I
|
1494
|
+
|
1495
|
+
I --> J{Strategy Selection}
|
1496
|
+
J --> K[distributed_atomic]
|
1497
|
+
J --> L[distributed_basic]
|
1498
|
+
J --> M[local_only]
|
1499
|
+
|
1500
|
+
classDef priority fill:#e1f5fe,stroke:#01579b
|
1501
|
+
classDef strategy fill:#f3e5f5,stroke:#4a148c
|
1502
|
+
|
1503
|
+
class B,D,F priority
|
1504
|
+
class K,L,M strategy
|
1505
|
+
```
|
1506
|
+
|
1507
|
+
### Built-in Store Support
|
1508
|
+
|
1509
|
+
Tasker includes **frozen constants** for fast, reliable detection of official Rails cache stores:
|
1510
|
+
|
1511
|
+
```ruby
|
1512
|
+
# Automatically detected with O(1) lookup
|
1513
|
+
DISTRIBUTED_CACHE_STORES = %w[
|
1514
|
+
ActiveSupport::Cache::RedisCacheStore
|
1515
|
+
ActiveSupport::Cache::MemCacheStore
|
1516
|
+
SolidCache::Store
|
1517
|
+
].freeze
|
1518
|
+
|
1519
|
+
ATOMIC_INCREMENT_STORES = %w[
|
1520
|
+
ActiveSupport::Cache::RedisCacheStore
|
1521
|
+
ActiveSupport::Cache::MemCacheStore
|
1522
|
+
SolidCache::Store
|
1523
|
+
].freeze
|
1524
|
+
|
1525
|
+
LOCKING_CAPABLE_STORES = %w[
|
1526
|
+
ActiveSupport::Cache::RedisCacheStore
|
1527
|
+
SolidCache::Store
|
1528
|
+
].freeze
|
1529
|
+
|
1530
|
+
LOCAL_CACHE_STORES = %w[
|
1531
|
+
ActiveSupport::Cache::MemoryStore
|
1532
|
+
ActiveSupport::Cache::FileStore
|
1533
|
+
ActiveSupport::Cache::NullStore
|
1534
|
+
].freeze
|
1535
|
+
```
|
1536
|
+
|
1537
|
+
**Coordination Strategies by Store Type**:
|
1538
|
+
- **Redis/SolidCache**: `distributed_atomic` (full distributed coordination with locking)
|
1539
|
+
- **Memcached**: `distributed_basic` (distributed coordination without locking)
|
1540
|
+
- **Memory/File/Null**: `local_only` (single-process coordination)
|
1541
|
+
|
1542
|
+
### Custom Cache Store Integration
|
1543
|
+
|
1544
|
+
#### Using CacheCapabilities Module
|
1545
|
+
|
1546
|
+
The `Tasker::CacheCapabilities` module provides a clean API for declaring your cache store's capabilities:
|
1547
|
+
|
1548
|
+
```ruby
|
1549
|
+
class MyRedisCluster < ActiveSupport::Cache::Store
|
1550
|
+
include Tasker::CacheCapabilities
|
1551
|
+
|
1552
|
+
# Convenience methods for common capabilities
|
1553
|
+
supports_distributed_caching! # Sets distributed: true
|
1554
|
+
supports_atomic_increment! # Sets atomic_increment: true
|
1555
|
+
supports_locking! # Sets locking: true
|
1556
|
+
supports_ttl_inspection! # Sets ttl_inspection: true
|
1557
|
+
supports_namespace_isolation! # Sets namespace_support: true
|
1558
|
+
supports_compression! # Sets compression_support: true
|
1559
|
+
|
1560
|
+
# Custom capabilities specific to your implementation
|
1561
|
+
declare_cache_capability(:cluster_failover, true)
|
1562
|
+
declare_cache_capability(:read_replicas, true)
|
1563
|
+
declare_cache_capability(:geo_replication, false)
|
1564
|
+
|
1565
|
+
# Your cache store implementation...
|
1566
|
+
def read(name, options = nil)
|
1567
|
+
# Implementation
|
1568
|
+
end
|
1569
|
+
|
1570
|
+
def write(name, value, options = nil)
|
1571
|
+
# Implementation
|
1572
|
+
end
|
1573
|
+
|
1574
|
+
# Optional: Implement advanced features
|
1575
|
+
def increment(name, amount = 1, options = nil)
|
1576
|
+
# Atomic increment implementation
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
def with_lock(name, options = {}, &block)
|
1580
|
+
# Distributed locking implementation
|
1581
|
+
end
|
1582
|
+
end
|
1583
|
+
```
|
1584
|
+
|
1585
|
+
#### Capability Reference
|
1586
|
+
|
1587
|
+
| Capability | Description | Impact |
|
1588
|
+
|------------|-------------|---------|
|
1589
|
+
| `distributed` | Cache is shared across processes/containers | Enables distributed coordination |
|
1590
|
+
| `atomic_increment` | Supports atomic increment/decrement operations | Enables optimistic locking patterns |
|
1591
|
+
| `locking` | Supports distributed locking (with_lock) | Enables `distributed_atomic` strategy |
|
1592
|
+
| `ttl_inspection` | Can inspect/extend TTL of cached entries | Enables advanced TTL management |
|
1593
|
+
| `namespace_support` | Supports cache key namespacing | Enables namespace isolation |
|
1594
|
+
| `compression_support` | Supports automatic value compression | Enables bandwidth optimization |
|
1595
|
+
|
1596
|
+
#### Chaining and Inheritance
|
1597
|
+
|
1598
|
+
```ruby
|
1599
|
+
class BaseDistributedStore < ActiveSupport::Cache::Store
|
1600
|
+
include Tasker::CacheCapabilities
|
1601
|
+
|
1602
|
+
supports_distributed_caching!
|
1603
|
+
supports_ttl_inspection!
|
1604
|
+
end
|
1605
|
+
|
1606
|
+
class RedisClusterStore < BaseDistributedStore
|
1607
|
+
# Inherits distributed and ttl_inspection capabilities
|
1608
|
+
supports_atomic_increment!
|
1609
|
+
supports_locking!
|
1610
|
+
|
1611
|
+
# Override inherited capabilities if needed
|
1612
|
+
declare_cache_capability(:ttl_inspection, false) # Override parent
|
1613
|
+
end
|
1614
|
+
|
1615
|
+
class MemcachedClusterStore < BaseDistributedStore
|
1616
|
+
# Different capabilities for different technology
|
1617
|
+
supports_atomic_increment!
|
1618
|
+
# Note: Memcached doesn't support distributed locking
|
1619
|
+
end
|
1620
|
+
```
|
1621
|
+
|
1622
|
+
### Advanced Usage Patterns
|
1623
|
+
|
1624
|
+
#### Custom Detector Registration
|
1625
|
+
|
1626
|
+
For complex detection logic that can't be expressed through simple capability declarations:
|
1627
|
+
|
1628
|
+
```ruby
|
1629
|
+
# Register a custom detector for pattern-based detection
|
1630
|
+
Tasker::CacheStrategy.register_detector(/MyCustomCache/, lambda do |store|
|
1631
|
+
{
|
1632
|
+
distributed: store.respond_to?(:cluster_nodes) && store.cluster_nodes.size > 1,
|
1633
|
+
atomic_increment: store.respond_to?(:atomic_ops),
|
1634
|
+
locking: store.respond_to?(:distributed_lock),
|
1635
|
+
custom_feature: store.respond_to?(:advanced_query)
|
1636
|
+
}
|
1637
|
+
end)
|
1638
|
+
|
1639
|
+
# The detector will be applied automatically during detection
|
1640
|
+
strategy = Tasker::CacheStrategy.detect
|
1641
|
+
```
|
1642
|
+
|
1643
|
+
#### Runtime Strategy Inspection
|
1644
|
+
|
1645
|
+
```ruby
|
1646
|
+
strategy = Tasker::CacheStrategy.detect
|
1647
|
+
|
1648
|
+
# Check coordination mode
|
1649
|
+
puts strategy.coordination_mode # => :distributed_atomic
|
1650
|
+
|
1651
|
+
# Check specific capabilities
|
1652
|
+
puts strategy.supports?(:distributed) # => true
|
1653
|
+
puts strategy.supports?(:locking) # => true
|
1654
|
+
puts strategy.supports?(:custom_feature) # => true
|
1655
|
+
|
1656
|
+
# Export all capabilities (useful for debugging)
|
1657
|
+
puts strategy.export_capabilities
|
1658
|
+
# => {
|
1659
|
+
# distributed: true,
|
1660
|
+
# atomic_increment: true,
|
1661
|
+
# locking: true,
|
1662
|
+
# ttl_inspection: true,
|
1663
|
+
# namespace_support: true,
|
1664
|
+
# compression_support: false,
|
1665
|
+
# key_transformation: true,
|
1666
|
+
# store_class: "MyAwesomeCacheStore"
|
1667
|
+
# }
|
1668
|
+
```
|
1669
|
+
|
1670
|
+
#### Production Monitoring
|
1671
|
+
|
1672
|
+
The cache strategy system includes comprehensive structured logging:
|
1673
|
+
|
1674
|
+
```ruby
|
1675
|
+
# Automatic logging when strategy is detected
|
1676
|
+
strategy = Tasker::CacheStrategy.detect
|
1677
|
+
|
1678
|
+
# Logs output (JSON format):
|
1679
|
+
{
|
1680
|
+
"timestamp": "2025-06-28T17:15:53.386Z",
|
1681
|
+
"correlation_id": "tsk_mcgi5my9_a24eXc",
|
1682
|
+
"component": "cache_strategy",
|
1683
|
+
"message": "Cache strategy detected",
|
1684
|
+
"environment": "production",
|
1685
|
+
"tasker_version": "2.5.0",
|
1686
|
+
"store_class": "MyAwesomeCacheStore",
|
1687
|
+
"coordination_strategy": "distributed_atomic",
|
1688
|
+
"capabilities": {
|
1689
|
+
"distributed": true,
|
1690
|
+
"atomic_increment": true,
|
1691
|
+
"locking": true,
|
1692
|
+
"ttl_inspection": true,
|
1693
|
+
"namespace_support": true,
|
1694
|
+
"compression_support": false,
|
1695
|
+
"key_transformation": true,
|
1696
|
+
"store_class": "MyAwesomeCacheStore"
|
1697
|
+
},
|
1698
|
+
"instance_id": "web-01-12345"
|
1699
|
+
}
|
1700
|
+
```
|
1701
|
+
|
1702
|
+
### Best Practices
|
1703
|
+
|
1704
|
+
#### Cache Store Selection
|
1705
|
+
|
1706
|
+
**For High-Performance Applications**:
|
1707
|
+
```ruby
|
1708
|
+
# Redis with full capabilities
|
1709
|
+
class ProductionRedisStore < ActiveSupport::Cache::RedisCacheStore
|
1710
|
+
include Tasker::CacheCapabilities
|
1711
|
+
|
1712
|
+
supports_distributed_caching!
|
1713
|
+
supports_atomic_increment!
|
1714
|
+
supports_locking!
|
1715
|
+
supports_ttl_inspection!
|
1716
|
+
supports_namespace_isolation!
|
1717
|
+
supports_compression!
|
1718
|
+
end
|
1719
|
+
```
|
1720
|
+
|
1721
|
+
**For Development/Testing**:
|
1722
|
+
```ruby
|
1723
|
+
# Memory store with local-only coordination
|
1724
|
+
# No additional configuration needed - automatically detected
|
1725
|
+
Rails.application.configure do
|
1726
|
+
config.cache_store = :memory_store
|
1727
|
+
end
|
1728
|
+
```
|
1729
|
+
|
1730
|
+
**For Legacy Memcached**:
|
1731
|
+
```ruby
|
1732
|
+
class LegacyMemcachedStore < ActiveSupport::Cache::MemCacheStore
|
1733
|
+
include Tasker::CacheCapabilities
|
1734
|
+
|
1735
|
+
supports_distributed_caching!
|
1736
|
+
supports_atomic_increment!
|
1737
|
+
# Note: Don't declare locking support - Memcached doesn't support it
|
1738
|
+
supports_ttl_inspection!
|
1739
|
+
end
|
1740
|
+
```
|
1741
|
+
|
1742
|
+
#### Testing Custom Stores
|
1743
|
+
|
1744
|
+
```ruby
|
1745
|
+
# spec/lib/my_awesome_cache_store_spec.rb
|
1746
|
+
RSpec.describe MyAwesomeCacheStore do
|
1747
|
+
let(:store) { described_class.new }
|
1748
|
+
|
1749
|
+
describe 'capability declarations' do
|
1750
|
+
it 'declares expected capabilities' do
|
1751
|
+
capabilities = store.class.declared_cache_capabilities
|
1752
|
+
|
1753
|
+
expect(capabilities[:distributed]).to be(true)
|
1754
|
+
expect(capabilities[:atomic_increment]).to be(true)
|
1755
|
+
expect(capabilities[:locking]).to be(true)
|
1756
|
+
expect(capabilities[:advanced_analytics]).to be(true)
|
1757
|
+
expect(capabilities[:compression_support]).to be(false)
|
1758
|
+
end
|
1759
|
+
end
|
1760
|
+
|
1761
|
+
describe 'integration with CacheStrategy' do
|
1762
|
+
before do
|
1763
|
+
allow(Rails).to receive(:cache).and_return(store)
|
1764
|
+
end
|
1765
|
+
|
1766
|
+
it 'is detected correctly by CacheStrategy' do
|
1767
|
+
strategy = Tasker::CacheStrategy.detect
|
1768
|
+
|
1769
|
+
expect(strategy.coordination_mode).to eq(:distributed_atomic)
|
1770
|
+
expect(strategy.supports?(:distributed)).to be(true)
|
1771
|
+
expect(strategy.supports?(:advanced_analytics)).to be(true)
|
1772
|
+
end
|
1773
|
+
end
|
1774
|
+
end
|
1775
|
+
```
|
1776
|
+
|
1777
|
+
### Migration Guide
|
1778
|
+
|
1779
|
+
#### Upgrading Existing Custom Stores
|
1780
|
+
|
1781
|
+
If you have existing custom cache stores, add capability declarations:
|
1782
|
+
|
1783
|
+
```ruby
|
1784
|
+
# Before: No capability declarations
|
1785
|
+
class MyLegacyStore < ActiveSupport::Cache::Store
|
1786
|
+
# Implementation...
|
1787
|
+
end
|
1788
|
+
|
1789
|
+
# After: With capability declarations
|
1790
|
+
class MyLegacyStore < ActiveSupport::Cache::Store
|
1791
|
+
include Tasker::CacheCapabilities
|
1792
|
+
|
1793
|
+
# Declare what your store actually supports
|
1794
|
+
supports_distributed_caching! if respond_to?(:cluster_mode?)
|
1795
|
+
supports_atomic_increment! if respond_to?(:increment)
|
1796
|
+
# etc.
|
1797
|
+
end
|
1798
|
+
```
|
1799
|
+
|
1800
|
+
#### Backwards Compatibility
|
1801
|
+
|
1802
|
+
The system maintains full backwards compatibility:
|
1803
|
+
- **Undeclared stores**: Fall back to runtime pattern detection
|
1804
|
+
- **Existing configurations**: Continue working without changes
|
1805
|
+
- **Built-in stores**: Automatically detected with frozen constants
|
1806
|
+
|
1807
|
+
The cache strategy system provides a robust foundation for building cache-aware applications that adapt intelligently to different deployment environments while maintaining optimal performance characteristics.
|
1808
|
+
|
1809
|
+
## Extensibility & Advanced Patterns
|
1810
|
+
|
1811
|
+
### Custom Step Handler Types
|
1812
|
+
|
1813
|
+
Beyond the built-in `Base` and `Api` step handlers, you can create specialized step handler types for common patterns in your application:
|
1814
|
+
|
1815
|
+
```ruby
|
1816
|
+
# Base class for database-heavy operations
|
1817
|
+
module YourApp
|
1818
|
+
module StepHandler
|
1819
|
+
class DatabaseBase < Tasker::StepHandler::Base
|
1820
|
+
protected
|
1821
|
+
|
1822
|
+
def with_transaction(&block)
|
1823
|
+
ActiveRecord::Base.transaction(&block)
|
1824
|
+
rescue ActiveRecord::StatementInvalid => e
|
1825
|
+
# Transform database errors for consistent handling
|
1826
|
+
raise Tasker::RetryableError, "Database operation failed: #{e.message}"
|
1827
|
+
end
|
1828
|
+
|
1829
|
+
def bulk_insert(model_class, records, batch_size: 1000)
|
1830
|
+
records.each_slice(batch_size) do |batch|
|
1831
|
+
model_class.insert_all(batch)
|
1832
|
+
end
|
1833
|
+
end
|
1834
|
+
end
|
1835
|
+
end
|
1836
|
+
end
|
1837
|
+
|
1838
|
+
# Usage in your step handlers
|
1839
|
+
class ProcessBulkOrdersHandler < YourApp::StepHandler::DatabaseBase
|
1840
|
+
def process(task, sequence, step)
|
1841
|
+
orders_data = get_previous_step_data(sequence, 'fetch_orders', 'orders')
|
1842
|
+
|
1843
|
+
with_transaction do
|
1844
|
+
bulk_insert(Order, orders_data, batch_size: 500)
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
{ processed_count: orders_data.size }
|
1848
|
+
end
|
1849
|
+
end
|
1850
|
+
```
|
1851
|
+
|
1852
|
+
### Custom Event Publishing Patterns
|
1853
|
+
|
1854
|
+
Create reusable event publishing patterns for consistent observability:
|
1855
|
+
|
1856
|
+
```ruby
|
1857
|
+
# Mixin for consistent business event publishing
|
1858
|
+
module BusinessEventPublisher
|
1859
|
+
extend ActiveSupport::Concern
|
1860
|
+
|
1861
|
+
def publish_business_event(domain, action, entity_id, data = {})
|
1862
|
+
event_name = "#{domain}.#{action}"
|
1863
|
+
event_data = {
|
1864
|
+
entity_id: entity_id,
|
1865
|
+
domain: domain,
|
1866
|
+
action: action,
|
1867
|
+
timestamp: Time.current.iso8601,
|
1868
|
+
**data
|
1869
|
+
}
|
1870
|
+
|
1871
|
+
publish_custom_event(event_name, event_data)
|
1872
|
+
end
|
1873
|
+
end
|
1874
|
+
|
1875
|
+
# Usage in step handlers
|
1876
|
+
class ProcessOrderHandler < Tasker::StepHandler::Base
|
1877
|
+
include BusinessEventPublisher
|
1878
|
+
|
1879
|
+
def process(task, sequence, step)
|
1880
|
+
order = Order.find(task.context['order_id'])
|
1881
|
+
|
1882
|
+
# Process order logic here...
|
1883
|
+
order.update!(status: 'processed')
|
1884
|
+
|
1885
|
+
# Publish consistent business event
|
1886
|
+
publish_business_event('order', 'processed', order.id, {
|
1887
|
+
customer_id: order.customer_id,
|
1888
|
+
total_amount: order.total,
|
1889
|
+
processing_duration: Time.current - order.created_at
|
1890
|
+
})
|
1891
|
+
|
1892
|
+
{ order_id: order.id, status: 'processed' }
|
1893
|
+
end
|
1894
|
+
end
|
1895
|
+
```
|
1896
|
+
|
1897
|
+
### Advanced Configuration Patterns
|
1898
|
+
|
1899
|
+
**Environment-Specific Step Configuration**:
|
1900
|
+
```yaml
|
1901
|
+
# config/tasker/tasks/order_process/order_handler.yaml
|
1902
|
+
step_templates:
|
1903
|
+
- name: send_notification
|
1904
|
+
handler_class: OrderProcess::SendNotificationHandler
|
1905
|
+
handler_config:
|
1906
|
+
<% if Rails.env.production? %>
|
1907
|
+
notification_service: sms_gateway
|
1908
|
+
priority: high
|
1909
|
+
<% else %>
|
1910
|
+
notification_service: email_only
|
1911
|
+
priority: low
|
1912
|
+
<% end %>
|
1913
|
+
```
|
1914
|
+
|
1915
|
+
**Dynamic Step Generation**:
|
1916
|
+
```ruby
|
1917
|
+
# Support for dynamically generated workflows
|
1918
|
+
class DynamicOrderProcess < Tasker::TaskHandler::Base
|
1919
|
+
def self.generate_step_templates(product_types)
|
1920
|
+
base_steps = [
|
1921
|
+
{ name: 'validate_order', handler_class: 'OrderProcess::ValidateOrderHandler' }
|
1922
|
+
]
|
1923
|
+
|
1924
|
+
# Generate product-specific steps
|
1925
|
+
product_steps = product_types.map do |type|
|
1926
|
+
{
|
1927
|
+
name: "process_#{type}",
|
1928
|
+
depends_on_step: 'validate_order',
|
1929
|
+
handler_class: "OrderProcess::Process#{type.camelize}Handler"
|
1930
|
+
}
|
1931
|
+
end
|
1932
|
+
|
1933
|
+
merge_step = {
|
1934
|
+
name: 'finalize_order',
|
1935
|
+
depends_on_steps: product_steps.map { |step| step[:name] },
|
1936
|
+
handler_class: 'OrderProcess::FinalizeOrderHandler'
|
1937
|
+
}
|
1938
|
+
|
1939
|
+
base_steps + product_steps + [merge_step]
|
1940
|
+
end
|
1941
|
+
end
|
1942
|
+
```
|
1943
|
+
|
1944
|
+
### Custom Workflow Coordination
|
1945
|
+
|
1946
|
+
**Priority-Based Step Execution**:
|
1947
|
+
```ruby
|
1948
|
+
module PriorityWorkflow
|
1949
|
+
class Coordinator < Tasker::Orchestration::Coordinator
|
1950
|
+
private
|
1951
|
+
|
1952
|
+
def prioritize_ready_steps(ready_steps)
|
1953
|
+
# Custom sorting based on step configuration
|
1954
|
+
ready_steps.sort_by do |step|
|
1955
|
+
priority = step.handler_config['priority'] || 'normal'
|
1956
|
+
case priority
|
1957
|
+
when 'critical' then 0
|
1958
|
+
when 'high' then 1
|
1959
|
+
when 'normal' then 2
|
1960
|
+
when 'low' then 3
|
1961
|
+
else 2
|
1962
|
+
end
|
1963
|
+
end
|
1964
|
+
end
|
1965
|
+
end
|
1966
|
+
end
|
1967
|
+
|
1968
|
+
# Configure in initializer
|
1969
|
+
Tasker.configuration do |config|
|
1970
|
+
config.orchestration.coordinator_class = 'PriorityWorkflow::Coordinator'
|
1971
|
+
end
|
1972
|
+
```
|
1973
|
+
|
1974
|
+
**Resource-Aware Execution**:
|
1975
|
+
```ruby
|
1976
|
+
class ResourceAwareStepHandler < Tasker::StepHandler::Base
|
1977
|
+
def process(task, sequence, step)
|
1978
|
+
# Check system resources before proceeding
|
1979
|
+
if system_under_load?
|
1980
|
+
# Delay execution by raising a retryable error
|
1981
|
+
raise Tasker::RetryableError, "System under load, retrying later"
|
1982
|
+
end
|
1983
|
+
|
1984
|
+
# Normal processing
|
1985
|
+
perform_resource_intensive_operation
|
1986
|
+
end
|
1987
|
+
|
1988
|
+
private
|
1989
|
+
|
1990
|
+
def system_under_load?
|
1991
|
+
# Check CPU, memory, or external service health
|
1992
|
+
cpu_usage > 80 || memory_usage > 90 || external_service_degraded?
|
1993
|
+
end
|
1994
|
+
end
|
1995
|
+
```
|
1996
|
+
|
1997
|
+
### Custom Result Processing
|
1998
|
+
|
1999
|
+
**Structured Result Schemas**:
|
2000
|
+
```ruby
|
2001
|
+
class SchemaValidatedStepHandler < Tasker::StepHandler::Base
|
2002
|
+
RESULT_SCHEMA = {
|
2003
|
+
type: 'object',
|
2004
|
+
required: ['status', 'data'],
|
2005
|
+
properties: {
|
2006
|
+
status: { type: 'string', enum: ['success', 'partial', 'warning'] },
|
2007
|
+
data: { type: 'object' },
|
2008
|
+
metadata: { type: 'object' }
|
2009
|
+
}
|
2010
|
+
}.freeze
|
2011
|
+
|
2012
|
+
def process(task, sequence, step)
|
2013
|
+
result = perform_business_logic
|
2014
|
+
|
2015
|
+
# Validate result structure
|
2016
|
+
validate_result_schema(result)
|
2017
|
+
|
2018
|
+
result
|
2019
|
+
end
|
2020
|
+
|
2021
|
+
private
|
2022
|
+
|
2023
|
+
def validate_result_schema(result)
|
2024
|
+
errors = JSON::Validator.fully_validate(RESULT_SCHEMA, result)
|
2025
|
+
raise ArgumentError, "Invalid result schema: #{errors.join(', ')}" if errors.any?
|
2026
|
+
end
|
2027
|
+
end
|
2028
|
+
```
|
2029
|
+
|
2030
|
+
**Result Transformation Pipelines**:
|
2031
|
+
```ruby
|
2032
|
+
module ResultTransformers
|
2033
|
+
class SensitiveDataFilter
|
2034
|
+
def self.transform(results)
|
2035
|
+
# Remove sensitive data from results before storage
|
2036
|
+
results.except('password', 'api_key', 'secret_token')
|
2037
|
+
end
|
2038
|
+
end
|
2039
|
+
|
2040
|
+
class MetricsExtractor
|
2041
|
+
def self.transform(results)
|
2042
|
+
# Extract metrics for monitoring
|
2043
|
+
{
|
2044
|
+
**results,
|
2045
|
+
performance_metrics: {
|
2046
|
+
execution_time: results['duration'],
|
2047
|
+
memory_used: results['memory_peak'],
|
2048
|
+
records_processed: results['record_count']
|
2049
|
+
}
|
2050
|
+
}
|
2051
|
+
end
|
2052
|
+
end
|
2053
|
+
end
|
2054
|
+
|
2055
|
+
class TransformingStepHandler < Tasker::StepHandler::Base
|
2056
|
+
def process_results(step, process_output, initial_results)
|
2057
|
+
# Apply transformation pipeline
|
2058
|
+
transformed = process_output
|
2059
|
+
transformed = ResultTransformers::SensitiveDataFilter.transform(transformed)
|
2060
|
+
transformed = ResultTransformers::MetricsExtractor.transform(transformed)
|
2061
|
+
|
2062
|
+
step.results = transformed
|
2063
|
+
end
|
2064
|
+
end
|
2065
|
+
```
|
2066
|
+
|
2067
|
+
## Task Execution & Orchestration
|
2068
|
+
|
2069
|
+
For detailed understanding of Tasker's sophisticated orchestration patterns, including the dual finalization strategy and coordinated vs autonomous execution contexts, see:
|
2070
|
+
|
2071
|
+
**📖 [Task Execution Control Flow](TASK_EXECUTION_CONTROL_FLOW.md)** - Comprehensive documentation of workflow orchestration patterns, synchronous vs asynchronous finalization, and the coordination between WorkflowCoordinator and TaskFinalizer components.
|
2072
|
+
|
2073
|
+
## Best Practices
|
2074
|
+
|
2075
|
+
### Task Handler Design
|
2076
|
+
|
2077
|
+
1. **Single Responsibility**: Each task handler should represent one business process
|
2078
|
+
2. **Meaningful Names**: Use descriptive names that clearly indicate the workflow purpose
|
2079
|
+
3. **Proper Dependencies**: Define clear step dependencies that reflect business logic
|
2080
|
+
4. **Error Handling**: Configure appropriate retry limits based on operation reliability
|
2081
|
+
5. **Context Design**: Include all necessary data in task context for step execution
|
2082
|
+
6. **Extensibility**: Design task handlers to support configuration-driven customization
|
2083
|
+
|
2084
|
+
### Step Handler Implementation
|
2085
|
+
|
2086
|
+
1. **Focused Logic**: Each step should do one thing well
|
2087
|
+
2. **Clear Results**: Return meaningful data structure for dependent steps
|
2088
|
+
3. **Error Handling**: Understand how the framework determines step success/failure (see Error Handling Patterns below)
|
2089
|
+
4. **Idempotency**: Design steps to be safely retryable
|
2090
|
+
5. **Resource Cleanup**: Clean up resources in error scenarios
|
2091
|
+
|
2092
|
+
#### Error Handling Patterns
|
2093
|
+
|
2094
|
+
The framework determines step success/failure based on whether the `process` method raises an exception:
|
2095
|
+
|
2096
|
+
- **Exception raised** → Step marked as FAILED, retry logic triggered, workflow stops
|
2097
|
+
- **No exception raised** → Step marked as COMPLETED, workflow continues
|
2098
|
+
|
2099
|
+
**Pattern 1: Let exceptions bubble up (Recommended)**
|
2100
|
+
```ruby
|
2101
|
+
def process(task, sequence, step)
|
2102
|
+
# Attempt the operation - let exceptions propagate naturally
|
2103
|
+
result = perform_complex_operation(task.context)
|
2104
|
+
{ success: true, data: result }
|
2105
|
+
|
2106
|
+
# Framework automatically handles exceptions:
|
2107
|
+
# - Publishes step_failed event with error details
|
2108
|
+
# - Stores error information in step.results
|
2109
|
+
# - Transitions step to error state
|
2110
|
+
# - Triggers retry logic if configured
|
2111
|
+
end
|
2112
|
+
```
|
2113
|
+
|
2114
|
+
**Pattern 2: Catch, record error details, then re-raise**
|
2115
|
+
```ruby
|
2116
|
+
def process(task, sequence, step)
|
2117
|
+
begin
|
2118
|
+
result = perform_complex_operation(task.context)
|
2119
|
+
{ success: true, data: result }
|
2120
|
+
rescue StandardError => e
|
2121
|
+
# Add custom error context to step.results
|
2122
|
+
step.results = {
|
2123
|
+
error: e.message,
|
2124
|
+
error_type: e.class.name,
|
2125
|
+
custom_context: "Additional business context",
|
2126
|
+
retry_recommended: should_retry?(e)
|
2127
|
+
}
|
2128
|
+
# Re-raise so framework knows this step failed
|
2129
|
+
raise
|
2130
|
+
end
|
2131
|
+
end
|
2132
|
+
```
|
2133
|
+
|
2134
|
+
**Pattern 3: Treat handled exceptions as success**
|
2135
|
+
```ruby
|
2136
|
+
def process(task, sequence, step)
|
2137
|
+
begin
|
2138
|
+
result = perform_complex_operation(task.context)
|
2139
|
+
{ success: true, data: result }
|
2140
|
+
rescue RecoverableError => e
|
2141
|
+
# This exception is handled and considered a success case
|
2142
|
+
# Step will be marked as COMPLETED, not failed
|
2143
|
+
{
|
2144
|
+
success: true,
|
2145
|
+
data: get_fallback_data(task.context),
|
2146
|
+
recovered_from_error: e.message,
|
2147
|
+
used_fallback: true
|
2148
|
+
}
|
2149
|
+
end
|
2150
|
+
end
|
2151
|
+
```
|
2152
|
+
|
2153
|
+
⚠️ **Important**: Only catch exceptions in your `process` method if you intend to either:
|
2154
|
+
- Add custom error context to `step.results` and re-raise (Pattern 2)
|
2155
|
+
- Treat the exception as a recoverable success case (Pattern 3)
|
2156
|
+
|
2157
|
+
**Never** catch an exception, return error data, and expect the framework to treat it as a failure - it will be marked as successful.
|
2158
|
+
|
2159
|
+
### Event Subscriber Guidelines
|
2160
|
+
|
2161
|
+
1. **Extend BaseSubscriber**: Always use `Tasker::Events::Subscribers::BaseSubscriber`
|
2162
|
+
2. **Safe Data Access**: Use `safe_get(event, :key, default)` for robust data access
|
2163
|
+
3. **Error Isolation**: Don't let subscriber errors break task execution
|
2164
|
+
4. **Async Operations**: Move heavy operations to background jobs
|
2165
|
+
5. **Monitoring**: Monitor subscriber performance and error rates
|
2166
|
+
|
2167
|
+
### YAML Configuration Management
|
2168
|
+
|
2169
|
+
1. **Environment Separation**: Use environment-specific configurations appropriately
|
2170
|
+
2. **Sensitive Data**: Use environment variables for secrets and API keys
|
2171
|
+
3. **Documentation**: Include clear descriptions for all steps
|
2172
|
+
4. **Validation**: Use JSON schema validation for task context requirements
|
2173
|
+
5. **Versioning**: Version control all YAML configuration files
|
2174
|
+
|
2175
|
+
## Development Workflow
|
2176
|
+
|
2177
|
+
### 1. Plan Your Workflow
|
2178
|
+
```bash
|
2179
|
+
# Define your business process steps and dependencies
|
2180
|
+
# Example: Order Processing
|
2181
|
+
# 1. Validate Order → 2. Process Payment → 3. Ship Order
|
2182
|
+
```
|
2183
|
+
|
2184
|
+
### 2. Generate Task Handler
|
2185
|
+
```bash
|
2186
|
+
rails generate tasker:task_handler OrderHandler --module_namespace OrderProcess --steps="validate_order,process_payment,ship_order"
|
2187
|
+
```
|
2188
|
+
|
2189
|
+
### 3. Implement Step Handlers
|
2190
|
+
```ruby
|
2191
|
+
# app/tasks/order_process/validate_order_handler.rb
|
2192
|
+
class ValidateOrderHandler < Tasker::StepHandler::Base
|
2193
|
+
def self.custom_event_configuration
|
2194
|
+
[
|
2195
|
+
{ name: 'order.validation_failed', description: 'Order validation failed' },
|
2196
|
+
{ name: 'order.validated', description: 'Order validation successful' }
|
2197
|
+
]
|
2198
|
+
end
|
2199
|
+
|
2200
|
+
def process(task, sequence, step)
|
2201
|
+
# Implementation here
|
2202
|
+
# Publish custom events as needed
|
2203
|
+
end
|
2204
|
+
end
|
2205
|
+
```
|
2206
|
+
|
2207
|
+
### 4. Configure Dependencies
|
2208
|
+
```yaml
|
2209
|
+
# config/tasker/tasks/order_process/order_handler.yaml
|
2210
|
+
step_templates:
|
2211
|
+
- name: validate_order
|
2212
|
+
handler_class: OrderProcess::ValidateOrderHandler
|
2213
|
+
- name: process_payment
|
2214
|
+
depends_on_step: validate_order
|
2215
|
+
handler_class: OrderProcess::ProcessPaymentHandler
|
2216
|
+
custom_events:
|
2217
|
+
- name: payment.gateway_error
|
2218
|
+
description: Payment gateway returned an error
|
2219
|
+
- name: ship_order
|
2220
|
+
depends_on_step: process_payment
|
2221
|
+
handler_class: OrderProcess::ShipOrderHandler
|
2222
|
+
```
|
2223
|
+
|
2224
|
+
### 5. Test Your Workflow
|
2225
|
+
```ruby
|
2226
|
+
# spec/tasks/order_process/order_handler_spec.rb
|
2227
|
+
RSpec.describe OrderProcess::OrderHandler do
|
2228
|
+
it 'processes orders successfully' do
|
2229
|
+
# Test implementation
|
2230
|
+
end
|
2231
|
+
end
|
2232
|
+
```
|
2233
|
+
|
2234
|
+
### 6. Deploy and Monitor
|
2235
|
+
- Custom events are automatically registered and discoverable
|
2236
|
+
- External systems can subscribe to your custom events
|
2237
|
+
- Monitor workflow execution through event subscriptions
|
2238
|
+
|
2239
|
+
## Testing Strategies
|
2240
|
+
|
2241
|
+
### Testing Task Handlers
|
2242
|
+
```ruby
|
2243
|
+
RSpec.describe OrderProcess::OrderHandler do
|
2244
|
+
describe '#initialize_task!' do
|
2245
|
+
let(:task_request) do
|
2246
|
+
Tasker::Types::TaskRequest.new(
|
2247
|
+
name: 'order_process',
|
2248
|
+
context: { order_id: 12345, customer_id: 67890 }
|
2249
|
+
)
|
2250
|
+
end
|
2251
|
+
|
2252
|
+
it 'creates task with proper step configuration' do
|
2253
|
+
handler = described_class.new
|
2254
|
+
task = handler.initialize_task!(task_request)
|
2255
|
+
|
2256
|
+
expect(task.workflow_steps.count).to eq(5)
|
2257
|
+
expect(task.workflow_steps.pluck(:name)).to include('fetch_order', 'process_payment')
|
2258
|
+
end
|
2259
|
+
end
|
2260
|
+
end
|
2261
|
+
```
|
2262
|
+
|
2263
|
+
### Testing Step Handlers
|
2264
|
+
```ruby
|
2265
|
+
RSpec.describe OrderProcess::StepHandler::ProcessPaymentHandler do
|
2266
|
+
describe '#process' do
|
2267
|
+
let(:task) { create(:task, context: { order_id: 123, payment_method: 'credit_card' }) }
|
2268
|
+
let(:sequence) { build_sequence_for(task) }
|
2269
|
+
let(:step) { sequence.find_step_by_name('process_payment') }
|
2270
|
+
|
2271
|
+
it 'processes payment and returns payment details' do
|
2272
|
+
handler = described_class.new
|
2273
|
+
result = handler.process(task, sequence, step)
|
2274
|
+
|
2275
|
+
expect(result).to include(:payment_id, :amount_charged, :transaction_id)
|
2276
|
+
end
|
2277
|
+
end
|
2278
|
+
end
|
2279
|
+
```
|
2280
|
+
|
2281
|
+
### Testing Event Subscribers
|
2282
|
+
```ruby
|
2283
|
+
RSpec.describe NotificationSubscriber do
|
2284
|
+
describe '#handle_task_completed' do
|
2285
|
+
let(:event) { { task_id: 'task_123', task_name: 'order_process', execution_duration: 45.2 } }
|
2286
|
+
|
2287
|
+
it 'sends completion notification' do
|
2288
|
+
subscriber = described_class.new
|
2289
|
+
|
2290
|
+
expect(NotificationService).to receive(:send_email)
|
2291
|
+
.with(hash_including(subject: /Task Completed/))
|
2292
|
+
|
2293
|
+
subscriber.handle_task_completed(event)
|
2294
|
+
end
|
2295
|
+
end
|
2296
|
+
end
|
2297
|
+
```
|
2298
|
+
|
2299
|
+
## Integration Validation & Production Readiness
|
2300
|
+
|
2301
|
+
Tasker includes comprehensive integration validation scripts that prove production readiness through real-world testing of the complete observability stack. These scripts are essential for validating enterprise deployments and ensuring reliable metrics collection and distributed tracing.
|
2302
|
+
|
2303
|
+
### Validation Script Architecture
|
2304
|
+
|
2305
|
+
The validation scripts (`scripts/validate_*.rb`) provide **enterprise-grade validation** of Tasker's integration with observability stack components:
|
2306
|
+
|
2307
|
+
#### **Jaeger Integration Validator** (`validate_jaeger_integration.rb`)
|
2308
|
+
Validates OpenTelemetry distributed tracing integration:
|
2309
|
+
|
2310
|
+
```bash
|
2311
|
+
# Run comprehensive Jaeger validation
|
2312
|
+
./scripts/validate_jaeger_integration.rb
|
2313
|
+
```
|
2314
|
+
|
2315
|
+
**Validation Categories**:
|
2316
|
+
- **Connection Testing**: Verifies Jaeger HTTP API connectivity
|
2317
|
+
- **Workflow Execution**: Creates and executes real workflow patterns (linear, diamond, parallel)
|
2318
|
+
- **Trace Collection**: Validates trace export and collection in Jaeger
|
2319
|
+
- **Span Hierarchy**: Analyzes parent-child relationships across workflow steps
|
2320
|
+
- **Trace Correlation**: Ensures proper trace correlation across distributed operations
|
2321
|
+
|
2322
|
+
**Sample Results**:
|
2323
|
+
```
|
2324
|
+
🎯 Tasker 2.5.0 - Jaeger Integration Validator
|
2325
|
+
✅ Jaeger Connection: PASS - Successfully connected to Jaeger
|
2326
|
+
✅ Workflow Execution: PASS - Created and executed 3 workflows
|
2327
|
+
✅ Trace Collection: PASS - Successfully collected 13 spans
|
2328
|
+
✅ Span Hierarchy: PASS - Validated 10 parent-child relationships
|
2329
|
+
✅ Trace Correlation: PASS - All spans properly correlated
|
2330
|
+
|
2331
|
+
📊 Span Analysis Results:
|
2332
|
+
Linear Workflow: 4 spans, 3 parent-child relationships
|
2333
|
+
Diamond Workflow: 5 spans, 4 parent-child relationships
|
2334
|
+
Parallel Workflow: 4 spans, 3 parent-child relationships
|
2335
|
+
```
|
2336
|
+
|
2337
|
+
#### **Prometheus Integration Validator** (`validate_prometheus_integration.rb`)
|
2338
|
+
Validates metrics collection and Prometheus integration:
|
2339
|
+
|
2340
|
+
```bash
|
2341
|
+
# Run comprehensive Prometheus validation
|
2342
|
+
./scripts/validate_prometheus_integration.rb
|
2343
|
+
```
|
2344
|
+
|
2345
|
+
**Validation Categories**:
|
2346
|
+
- **Prometheus Connection**: Verifies Prometheus server connectivity
|
2347
|
+
- **Metrics Endpoint**: Tests Tasker's `/tasker/metrics` endpoint
|
2348
|
+
- **Workflow Execution**: Executes workflows to generate authentic metrics
|
2349
|
+
- **Metrics Collection**: Validates event-driven metrics collection via EventRouter
|
2350
|
+
- **Query Validation**: Tests PromQL queries for dashboard compatibility
|
2351
|
+
- **Performance Analysis**: Analyzes TSDB integration and performance
|
2352
|
+
|
2353
|
+
**Sample Results**:
|
2354
|
+
```
|
2355
|
+
🎯 Tasker 2.5.0 - Prometheus Integration Validator
|
2356
|
+
✅ MetricsSubscriber registered successfully
|
2357
|
+
✅ Prometheus Connection: PASS - Successfully connected to Prometheus
|
2358
|
+
✅ Metrics Endpoint: PASS - Tasker metrics endpoint accessible
|
2359
|
+
✅ Workflow Execution: PASS - Created and executed 3 workflows
|
2360
|
+
✅ Metrics Collection: PASS - Successfully collected 3 total metrics
|
2361
|
+
✅ Query Validation: PASS - All 4 PromQL queries successful
|
2362
|
+
|
2363
|
+
📊 Metrics Analysis Results:
|
2364
|
+
Counter metrics: 2 (step_completed_total: 22, task_completed_total: 3)
|
2365
|
+
Histogram metrics: 1 (step_duration_seconds)
|
2366
|
+
Total workflow activity: 22 steps completed across 3 tasks
|
2367
|
+
```
|
2368
|
+
|
2369
|
+
### Event-Driven Architecture Validation
|
2370
|
+
|
2371
|
+
The validation scripts prove Tasker's sophisticated event-driven architecture:
|
2372
|
+
|
2373
|
+
```mermaid
|
2374
|
+
flowchart LR
|
2375
|
+
subgraph Validation["Integration Validation"]
|
2376
|
+
WF["Workflow<br/>Execution"]
|
2377
|
+
EP["Event<br/>Publishing"]
|
2378
|
+
end
|
2379
|
+
|
2380
|
+
subgraph EventSystem["Event System"]
|
2381
|
+
PUB["Events::<br/>Publisher"]
|
2382
|
+
TS["Telemetry<br/>Subscriber"]
|
2383
|
+
MS["Metrics<br/>Subscriber"]
|
2384
|
+
end
|
2385
|
+
|
2386
|
+
subgraph Observability["Observability Stack"]
|
2387
|
+
OT["OpenTelemetry<br/>Spans"]
|
2388
|
+
ER["EventRouter<br/>Metrics"]
|
2389
|
+
J["Jaeger<br/>Tracing"]
|
2390
|
+
P["Prometheus<br/>Metrics"]
|
2391
|
+
end
|
2392
|
+
|
2393
|
+
WF --> EP
|
2394
|
+
EP --> PUB
|
2395
|
+
PUB --> TS
|
2396
|
+
PUB --> MS
|
2397
|
+
TS --> OT
|
2398
|
+
MS --> ER
|
2399
|
+
OT --> J
|
2400
|
+
ER --> P
|
2401
|
+
|
2402
|
+
classDef validation fill:#e1f5fe,stroke:#01579b
|
2403
|
+
classDef events fill:#fff3e0,stroke:#e65100
|
2404
|
+
classDef observability fill:#e8f5e8,stroke:#2e7d32
|
2405
|
+
|
2406
|
+
class WF,EP validation
|
2407
|
+
class PUB,TS,MS events
|
2408
|
+
class OT,ER,J,P observability
|
2409
|
+
```
|
2410
|
+
|
2411
|
+
### Critical Technical Breakthrough: MetricsSubscriber
|
2412
|
+
|
2413
|
+
The Prometheus validator discovered and resolved a **critical missing component** in Tasker's metrics architecture:
|
2414
|
+
|
2415
|
+
**Problem**: Events were being published via `Events::Publisher` and creating OpenTelemetry spans via `TelemetrySubscriber`, but **no metrics were being collected** because there was no bridge between the event system and the `EventRouter` → `MetricsBackend` system.
|
2416
|
+
|
2417
|
+
**Solution**: Created `MetricsSubscriber` that bridges events to the EventRouter:
|
2418
|
+
|
2419
|
+
```ruby
|
2420
|
+
# lib/tasker/events/subscribers/metrics_subscriber.rb
|
2421
|
+
class MetricsSubscriber < BaseSubscriber
|
2422
|
+
def handle_event(event_name, payload)
|
2423
|
+
# Bridge events to EventRouter for automatic metrics collection
|
2424
|
+
Tasker::Telemetry::EventRouter.instance.route_event(event_name, payload)
|
2425
|
+
rescue StandardError => e
|
2426
|
+
Rails.logger.error("MetricsSubscriber failed to route event: #{e.message}")
|
2427
|
+
end
|
2428
|
+
end
|
2429
|
+
```
|
2430
|
+
|
2431
|
+
**Integration**: The `MetricsSubscriber` is now automatically registered in the `Orchestration::Coordinator` alongside the `TelemetrySubscriber`, ensuring complete observability coverage.
|
2432
|
+
|
2433
|
+
### Production Deployment Validation
|
2434
|
+
|
2435
|
+
#### Prerequisites for Validation
|
2436
|
+
```bash
|
2437
|
+
# 1. Start observability stack
|
2438
|
+
docker-compose up -d jaeger prometheus
|
2439
|
+
|
2440
|
+
# 2. Ensure Tasker Rails application is running
|
2441
|
+
bundle exec rails server
|
2442
|
+
|
2443
|
+
# 3. Run validation scripts
|
2444
|
+
./scripts/validate_jaeger_integration.rb
|
2445
|
+
./scripts/validate_prometheus_integration.rb
|
2446
|
+
```
|
2447
|
+
|
2448
|
+
#### Continuous Integration Integration
|
2449
|
+
Both scripts are designed for CI/CD pipelines:
|
2450
|
+
|
2451
|
+
```yaml
|
2452
|
+
# .github/workflows/integration-validation.yml
|
2453
|
+
name: Integration Validation
|
2454
|
+
on: [push, pull_request]
|
2455
|
+
|
2456
|
+
jobs:
|
2457
|
+
validate-integrations:
|
2458
|
+
runs-on: ubuntu-latest
|
2459
|
+
services:
|
2460
|
+
jaeger:
|
2461
|
+
image: jaegertracing/all-in-one:latest
|
2462
|
+
ports:
|
2463
|
+
- 14268:14268
|
2464
|
+
prometheus:
|
2465
|
+
image: prom/prometheus:latest
|
2466
|
+
ports:
|
2467
|
+
- 9090:9090
|
2468
|
+
|
2469
|
+
steps:
|
2470
|
+
- uses: actions/checkout@v2
|
2471
|
+
- name: Setup Ruby
|
2472
|
+
uses: ruby/setup-ruby@v1
|
2473
|
+
with:
|
2474
|
+
bundler-cache: true
|
2475
|
+
|
2476
|
+
- name: Start Rails Application
|
2477
|
+
run: bundle exec rails server &
|
2478
|
+
|
2479
|
+
- name: Validate Jaeger Integration
|
2480
|
+
run: ./scripts/validate_jaeger_integration.rb
|
2481
|
+
|
2482
|
+
- name: Validate Prometheus Integration
|
2483
|
+
run: ./scripts/validate_prometheus_integration.rb
|
2484
|
+
```
|
2485
|
+
|
2486
|
+
#### Enterprise Deployment Checklist
|
2487
|
+
|
2488
|
+
**Pre-Deployment Validation**:
|
2489
|
+
- [ ] ✅ Jaeger integration validator passes (100% success rate)
|
2490
|
+
- [ ] ✅ Prometheus integration validator passes (100% success rate)
|
2491
|
+
- [ ] ✅ All observability stack components responding
|
2492
|
+
- [ ] ✅ Metrics collection functioning (non-zero metrics count)
|
2493
|
+
- [ ] ✅ Distributed tracing working (proper span hierarchies)
|
2494
|
+
- [ ] ✅ Dashboard queries validated (PromQL compatibility)
|
2495
|
+
|
2496
|
+
**Post-Deployment Monitoring**:
|
2497
|
+
```bash
|
2498
|
+
# Validate production observability stack
|
2499
|
+
PROMETHEUS_URL=https://prometheus.production.com \
|
2500
|
+
JAEGER_URL=https://jaeger.production.com \
|
2501
|
+
./scripts/validate_prometheus_integration.rb
|
2502
|
+
|
2503
|
+
# Monitor metrics collection in production
|
2504
|
+
bundle exec rake app:tasker:metrics_status
|
2505
|
+
```
|
2506
|
+
|
2507
|
+
### Development Workflow Integration
|
2508
|
+
|
2509
|
+
#### Local Development Setup
|
2510
|
+
```bash
|
2511
|
+
# 1. Start local observability stack
|
2512
|
+
docker-compose up -d
|
2513
|
+
|
2514
|
+
# 2. Run Tasker with observability enabled
|
2515
|
+
RAILS_ENV=development bundle exec rails server
|
2516
|
+
|
2517
|
+
# 3. Validate integrations during development
|
2518
|
+
./scripts/validate_jaeger_integration.rb
|
2519
|
+
./scripts/validate_prometheus_integration.rb
|
2520
|
+
```
|
2521
|
+
|
2522
|
+
#### Debugging Integration Issues
|
2523
|
+
The validation scripts provide comprehensive diagnostics:
|
2524
|
+
|
2525
|
+
```bash
|
2526
|
+
# Detailed diagnostic output for troubleshooting
|
2527
|
+
./scripts/validate_jaeger_integration.rb --verbose
|
2528
|
+
./scripts/validate_prometheus_integration.rb --verbose
|
2529
|
+
```
|
2530
|
+
|
2531
|
+
**Common Issues & Solutions**:
|
2532
|
+
- **No Metrics Collected**: Ensure `MetricsSubscriber` is registered (automatic in Tasker 2.5.0+)
|
2533
|
+
- **Missing Spans**: Verify OpenTelemetry exporter configuration
|
2534
|
+
- **Connection Failures**: Check service ports and network connectivity
|
2535
|
+
- **Query Failures**: Validate Prometheus data retention and configuration
|
2536
|
+
|
2537
|
+
### Strategic Value
|
2538
|
+
|
2539
|
+
The integration validation scripts provide:
|
2540
|
+
|
2541
|
+
- **🎯 Production Confidence**: Comprehensive proof of enterprise readiness
|
2542
|
+
- **🔧 Developer Experience**: Clear validation results with actionable feedback
|
2543
|
+
- **📊 Integration Foundation**: Proven patterns for observability stack integration
|
2544
|
+
- **📈 Content Creation**: Technical excellence provides foundation for documentation and presentations
|
2545
|
+
- **🚀 Enterprise Adoption**: Validated observability enables confident production deployment
|
2546
|
+
|
2547
|
+
**Status**: **COMPLETE** - Both Jaeger and Prometheus integration validators fully implemented and tested with 100% success rates, proving Tasker's production readiness through comprehensive observability stack validation.
|
2548
|
+
|
2549
|
+
## Application Template Validation (v2.6.0)
|
2550
|
+
|
2551
|
+
Tasker includes a comprehensive dry-run validation system for application template generation that ensures template consistency and correctness without generating files. This system is essential for maintaining template quality and CI/CD pipeline integration.
|
2552
|
+
|
2553
|
+
### Dry-Run Validation Overview
|
2554
|
+
|
2555
|
+
The validation system tests five critical categories of template correctness:
|
2556
|
+
|
2557
|
+
```bash
|
2558
|
+
# Run comprehensive validation
|
2559
|
+
ruby scripts/create_tasker_app.rb dry_run
|
2560
|
+
|
2561
|
+
# Run specific validation categories
|
2562
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=templates # File existence
|
2563
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=syntax # ERB/Ruby/YAML syntax
|
2564
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=cli # CLI option mapping
|
2565
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=bindings # Template variables
|
2566
|
+
|
2567
|
+
# Validate Docker templates
|
2568
|
+
ruby scripts/create_tasker_app.rb dry_run --docker --with-observability
|
2569
|
+
```
|
2570
|
+
|
2571
|
+
### Validation Categories
|
2572
|
+
|
2573
|
+
**1. Template File Existence Validation**
|
2574
|
+
- Verifies all required ERB templates exist for selected task types
|
2575
|
+
- Adapts template requirements based on Docker/observability modes
|
2576
|
+
- Reports missing templates with specific file paths
|
2577
|
+
|
2578
|
+
**2. ERB Template Syntax Validation**
|
2579
|
+
- Parses all ERB templates for syntax errors using `ERB.new(template).check_syntax`
|
2580
|
+
- Validates template structure without executing Ruby code
|
2581
|
+
- Catches malformed ERB blocks and Ruby expressions
|
2582
|
+
|
2583
|
+
**3. Generated Code Syntax Validation**
|
2584
|
+
- Renders templates with test data to validate actual output
|
2585
|
+
- Uses `RubyVM::InstructionSequence.compile` for Ruby syntax validation
|
2586
|
+
- Uses `YAML.safe_load` for YAML syntax validation
|
2587
|
+
- Tests real generated output, not just template correctness
|
2588
|
+
|
2589
|
+
**4. CLI Options Mapping Validation**
|
2590
|
+
- Ensures all Thor CLI options have corresponding instance variables
|
2591
|
+
- Validates option-to-variable mapping (e.g., `--docker` → `@docker_mode`)
|
2592
|
+
- Checks for missing or incorrectly mapped CLI options
|
2593
|
+
|
2594
|
+
**5. Template Variable Bindings Validation**
|
2595
|
+
- Tests template rendering with expected binding contexts
|
2596
|
+
- Validates all required variables are available during rendering
|
2597
|
+
- Tests step handlers, configuration files, and Docker-specific bindings
|
2598
|
+
|
2599
|
+
### CI/CD Integration
|
2600
|
+
|
2601
|
+
The dry-run validation system is designed for continuous integration:
|
2602
|
+
|
2603
|
+
```yaml
|
2604
|
+
# .github/workflows/template-validation.yml
|
2605
|
+
name: Template Validation
|
2606
|
+
on: [push, pull_request]
|
2607
|
+
|
2608
|
+
jobs:
|
2609
|
+
validate-templates:
|
2610
|
+
runs-on: ubuntu-latest
|
2611
|
+
steps:
|
2612
|
+
- uses: actions/checkout@v2
|
2613
|
+
- name: Setup Ruby
|
2614
|
+
uses: ruby/setup-ruby@v1
|
2615
|
+
with:
|
2616
|
+
bundler-cache: true
|
2617
|
+
|
2618
|
+
- name: Validate Application Templates
|
2619
|
+
run: |
|
2620
|
+
# Test all template combinations
|
2621
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=all
|
2622
|
+
ruby scripts/create_tasker_app.rb dry_run --docker --with-observability
|
2623
|
+
|
2624
|
+
- name: Validate Specific Categories
|
2625
|
+
run: |
|
2626
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=templates
|
2627
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=syntax
|
2628
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=cli
|
2629
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=bindings
|
2630
|
+
```
|
2631
|
+
|
2632
|
+
### Development Workflow Integration
|
2633
|
+
|
2634
|
+
**Template Development Process**:
|
2635
|
+
1. Modify ERB templates in `scripts/templates/`
|
2636
|
+
2. Run dry-run validation to catch issues early
|
2637
|
+
3. Fix validation errors before committing
|
2638
|
+
4. CI/CD pipeline validates all template combinations
|
2639
|
+
|
2640
|
+
**Local Development Validation**:
|
2641
|
+
```bash
|
2642
|
+
# Quick validation during template development
|
2643
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=syntax
|
2644
|
+
|
2645
|
+
# Full validation before committing
|
2646
|
+
ruby scripts/create_tasker_app.rb dry_run --mode=all
|
2647
|
+
|
2648
|
+
# Test Docker-specific templates
|
2649
|
+
ruby scripts/create_tasker_app.rb dry_run --docker --with-observability
|
2650
|
+
```
|
2651
|
+
|
2652
|
+
### Performance Characteristics
|
2653
|
+
|
2654
|
+
- **Zero File System Impact**: No files created during validation
|
2655
|
+
- **Sub-second Execution**: Validation completes in under 1 second
|
2656
|
+
- **Clear Exit Codes**: 0 for success, 1 for failure (CI/CD friendly)
|
2657
|
+
- **Detailed Error Reporting**: Specific file paths and line numbers for errors
|
2658
|
+
|
2659
|
+
### Strategic Benefits
|
2660
|
+
|
2661
|
+
- **Template Quality Assurance**: Prevents broken templates from reaching production
|
2662
|
+
- **Developer Confidence**: Comprehensive validation reduces template-related issues
|
2663
|
+
- **CI/CD Integration**: Automated validation in deployment pipelines
|
2664
|
+
- **Rapid Feedback**: Immediate validation results during development
|
2665
|
+
- **Enterprise Reliability**: Ensures consistent template generation across environments
|