Skip to main content

Backend Architecture

The apptor flow backend is a Gradle multi-module Java 21 project running on Micronaut 4.4.2. This page describes every module, how they relate to each other, and the key design decisions within each layer.


Module Dependency Tree


apptor-flow-sdk

Role: Domain model library. All other modules depend on this.

Key contents:

  • NodeType enum — 37 constants mapping JSON type strings to enum values (e.g., "aiTask"AI_TASK)
  • Node base class — all node model classes extend this
  • Specialized node classes: IfElseNode, InputNode, OutputNode, LoopNode, SetVariableNode
  • Variable, LoopCharacteristics, Condition, FormConfiguration
  • NodeEventType enum — CREATE, CANCEL, COMPLETE, TIMEOUT
  • ProcessDefinition, FlowDefinition — top-level workflow containers

Base Node fields:

String nodeId
String name
NodeType nodeType
List<String> outgoingSequenceFlowIds
List<String> incomingSequenceFlowIds
Map<String, Object> customProperties
Map<String, Variable> outputVariables
Map<String, Variable> inputVariables
String documentation
LoopType loopType // NONE | STANDARD_LOOP | MULTI_INSTANCE
LoopCharacteristics loopCharacteristics
Timeout timeout // {duration, uom, action}
Retry retry // {delay, uom, maxAttempts}
Map<NodeEventType, NodeEventHandler> eventHandlers

apptor-flow-json-parser

Role: Converts raw workflow JSON text into typed domain model objects.

Key classes:

  • Json2apptorTypeMapper — maps JSON type strings to NodeType enum constants
  • NodeContainerParser — top-level parser that dispatches to per-type parsers
  • YourNodeParser — one parser class per node type (e.g., IfElseNodeParser, AiTaskNodeParser)

Parser flow:

processText (String JSON)
→ Jackson ObjectMapper → raw Map/List
→ NodeContainerParser.parse()
→ per-type NodeParser.parse(rawNode)
→ typed Node subclass (e.g., IfElseNode)
→ FlowDefinition (complete typed graph)

Type mapping examples:

Map.entry("aiTask",       NodeType.AI_TASK)
Map.entry("ifElse", NodeType.IF_ELSE)
Map.entry("serviceTask", NodeType.SERVICE_TASK)
Map.entry("setVariable", NodeType.SET_VARIABLE)
Map.entry("loopNode", NodeType.LOOP_NODE)
Map.entry("inputNode", NodeType.INPUT_NODE)
Map.entry("outputNode", NodeType.OUTPUT_NODE)

apptor-actor-framework

Role: Generic actor system. Provides the thread pool and message queue infrastructure for the execution engine.

Concepts:

  • Actor — a named, typed message processor with a configurable thread count
  • ActorPool — manages a fixed pool of threads for one actor type
  • Message — a serializable envelope dispatched to an actor
  • Queue — pluggable queue backend (default: in-memory-distributed backed by Hazelcast)

Configuration (from flow-config.json):

{
"processActors": [
{ "name": "aiTask", "maxThreads": 1, "queueType": "in-memory-distributed" },
{ "name": "serviceTask", "maxThreads": 2, "queueType": "in-memory-distributed" },
...
]
}

The cacheType: "hazelcast" setting controls the distributed state store used for queue persistence.


apptor-flow-core

Role: The execution engine. Implements all 24 node handlers and manages process lifecycle.

Node Handler Lifecycle

Every node handler extends AbstractNodeHandler and implements:

void execute(NodeExecutionContext context)

The NodeExecutionContext provides:

  • Access to the current node definition
  • Read/write access to execution variables
  • Methods to dispatch the next node(s)
  • Access to services (integrations, secrets, etc.)

All 24 Node Handlers

Handler ClassNode TypeThreads
StartEventHandlerstartEvent5
EndEventHandlerendEvent1
TaskHandlertask2
UserTaskHandleruserTask2
ServiceTaskHandlerserviceTask2
ScriptTaskHandlerscriptTask2
AiTaskHandleraiTask1
VoiceTaskHandlervoiceTask2
IfElseNodeHandlerifElse1
ExclusiveGatewayHandlerexclusiveGateway1
InclusiveGatewayHandlerinclusiveGateway1
ParallelGatewayHandlerparallelGateway1
SubProcessHandlersubProcess1
CallProcessHandlercallProcess2
CallAgentHandlercallAgent2
LoopNodeHandlerloopNode1
BoundaryEventHandlerboundaryEvent1
IntermediateCatchEventHandlerintermediateCatchEvent1
IntermediateThrowEventHandlerintermediateThrowEvent1
InputNodeHandlerinputNode2
OutputNodeHandleroutputNode2
MemoryActionHandlermemory1
DomainActionHandlerdomainTask2
SetVariableHandlersetVariable2

AiTaskHandler — AI Integration Detail

The AiTaskHandler is the most feature-rich handler. It uses LangChain4j to:

  1. Build the system and user prompt (resolving {varName} template variables)
  2. Load conversation memory from ai_memory_conversation / ai_memory_message tables
  3. Register connected Tool nodes as LangChain4j tools
  4. Call the configured LLM provider (OpenAI, Anthropic, etc.) via aiProviderConnectionId
  5. Optionally stream the response to the chat interface (SSE / WebSocket)
  6. Generate A2UI component definitions if enableA2UI: true
  7. Write LLM output to the specified output variable

ScriptTaskHandler — GraalVM Integration

Script tasks execute via GraalVM's Polyglot API:

  • Supported languages: JavaScript, Python
  • Script has access to all workflow variables via the polyglot context
  • Output is written back as workflow variables

apptor-flow-api

Role: The Micronaut REST API layer. 41 controllers exposing all platform capabilities.

Controller base paths:

ControllerBase Path
ProcessController/process
TriggerController/trigger
ApiKeyController/api/keys
OrganizationController/organizations
UserController/users
RoleController/roles
IntegrationController/integrations
SecretController/secrets
EnvironmentController/environments
WebhookRegistrationController/api/webhooks — CRUD for webhook endpoints
WebhookIngressController/webhook/receive/{token} — public inbound receiver; /webhook/resume/{token} — wait node callback
WebhookEventController/api/webhooks/{id}/events — event log, replay, bulk replay
WebhookTestController/api/webhooks/{id}/test — test mode and signature validator
WebhookAuditController/api/admin/audit/webhooks — immutable audit log
WebhookAdminController/api/admin/webhooks — provider registry, system stats
WebhookGdprController/api/webhooks/gdpr — data export and purge
RagController/rag
FileStorageController/files
SuperAdminController/super-admin
ChatWorkflowWebSocketController/ws/chat/{processInstanceId}
A2UIActionController/a2ui/actions
VoiceCallbackController/voice/callback
PublishedWorkflowController/published-workflows
PublicWorkflowController/public/workflows
DomainController/domains
ObservabilityController/observability
EnvironmentController/environments

Webhook Module

The webhook module spans apptor-flow-core (verification + normalization) and apptor-flow-api (ingress, services, jobs, controllers). It is a dedicated subsystem with its own database tables, encryption, caching, and retry pipeline.

Core Components

ComponentLocationResponsibility
WebhookVerifier (interface)apptor-flow-coreVerify signature for one auth type
WebhookVerifierRegistryapptor-flow-coreMap authType → verifier; DI-discovered
WebhookNormalizer (interface)apptor-flow-coreExtract standard fields from provider payload
WebhookNormalizerRegistryapptor-flow-coreMap authType → normalizer
CustomWebhookVerifierapptor-flow-coreHandles all custom provider auth methods
CustomProviderConfigapptor-flow-corePOJO deserialized from custom_config_json
WaitNodeHandlerapptor-flow-corePause flow; register callback token; resume on POST
WebhookEncryptionServiceapptor-flow-apiAES-256-GCM encrypt/decrypt for secrets
WebhookSecretServiceapptor-flow-apiSecret lifecycle: create, rotate, cache, decrypt
WebhookRegistrationServiceapptor-flow-apiCRUD for registrations + Hazelcast cache invalidation
WebhookProcessorServiceapptor-flow-apiOrchestrate verify → normalize → trigger
WebhookAuditServiceapptor-flow-apiAppend-only audit log writes
WebhookNotificationServiceapptor-flow-apiIn-app notifications (circuit breaker, DLQ)
WebhookGdprServiceapptor-flow-apiGDPR data export and hard purge
WebhookRetrySchedulerJobapptor-flow-apiRe-queue PROCESSING_FAILED events (JobRunr)

Database Tables (V153–V159, V165)

TablePurpose
webhook_registrationEndpoint config: name, authType, status, circuit breaker counters
webhook_secretAES-256-GCM encrypted secrets; supports multiple ACTIVE for rotation overlap
webhook_eventFull event log: headers, payload, status, retry tracking, idempotency key
webhook_execution_tokenWait node callback tokens with timeout tracking
webhook_audit_logAppend-only 7-year audit trail; secrets never written here
webhook_provider_registryProvider lifecycle: BETA → GA → DEPRECATED → REMOVED
webhook_metrics_hourlyAggregated counts and latency per org/provider/endpoint
system_notificationIn-app notifications for circuit breaker trips and DLQ accumulation
system_notificationIn-app notifications for circuit breaker trips and DLQ accumulation

Secret Encryption

Secrets are encrypted with AES-256-GCM. Each encrypted value is stored as Base64(IV[12] || ciphertext || tag[16]). A fresh random IV is generated per encryption call, making ciphertext non-deterministic even for identical secrets. The key is loaded from apptor.secret.encryption-key and normalized to 32 bytes.

Supported Auth Types

Auth TypeVerification Method
GITHUBHMAC-SHA256 of body, sha256= prefix on X-Hub-Signature-256
GITLABConstant-time string comparison on X-Gitlab-Token
BITBUCKETHMAC-SHA256 of body on X-Hub-Signature
SHOPIFYHMAC-SHA256 Base64 of body on X-Shopify-Hmac-SHA256
WOOCOMMERCEHMAC-SHA256 Base64 of body on X-WC-Webhook-Signature
STRIPEHMAC-SHA256 of {ts}.{body} with ±5 min replay window
PADDLEHMAC-SHA256 of {ts}:{body} with ±5 min replay window
SLACKHMAC-SHA256 of v0:{ts}:{body} with ±5 min replay window
RAZORPAYHMAC-SHA256 of raw body bytes
SQUAREHMAC-SHA256 Base64 on x-square-hmacsha256-signature
STANDARD_WEBHOOKSStandard Webhooks spec: {webhook-id}.{ts}.{body}
BEARER_TOKENConstant-time comparison of Authorization: Bearer value
CUSTOMReads custom_config_json for method, header, template, format
NONEAlways passes — no verification

apptor-flow-persistence

Role: JPA entities, repositories, and Liquibase migration changelogs.

All entities extend BaseEntity which provides organizationId (multi-tenancy), createdAt, and updatedAt.

Key entity classes: ProcessEntity, ProcessVersionEntity, ProcessInstanceEntity, NodeInstanceEntity, ApiKeyEntity, RoleEntity, PermissionEntity, SecretEntity, IntegrationEntity, EnvironmentEntity.


apptor-secrets

Role: Encrypted storage for organization secrets. Values are encrypted at rest. Referenced in workflow properties as {env.SECRET_NAME}.


apptor-flow-vertical-integrations

Role: Provider-agnostic domain integration framework.

Concepts:

  • Vertical — a business domain (e.g., CRM, HRIS, Accounting)
  • Domain Action — an abstract operation within a vertical (e.g., "Create Contact")
  • Provider Mapping — maps the abstract action to a specific provider's API (e.g., Salesforce, HubSpot)
  • Integration Version — a versioned snapshot of a provider mapping

The Domain Task node executes a specific domain action via the configured provider mapping. This enables workflows to be provider-agnostic — swapping from Salesforce to HubSpot only requires updating the provider mapping, not the workflow.