Architecture Overview
Comprehensive architecture guide covering Elsa's components, execution model, data flow, and deployment patterns for architects and integrators.
This guide provides a comprehensive overview of Elsa Workflows' architecture, covering the major components, execution model, data flow, scalability considerations, and deployment topologies.
High-Level Architecture
Elsa Workflows is built on a modular, extensible architecture designed for flexibility and scalability. The system consists of several key layers that work together to enable workflow definition, execution, and management.
┌─────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ ┌────────────────┐ ┌────────────────┐ ┌───────────────┐ │
│ │ Elsa Studio │ │ REST APIs │ │ SignalR Hub │ │
│ │ (Blazor WASM) │ │ │ │ │ │
│ └────────────────┘ └────────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Workflow │ │ Activity │ │ Trigger │ │
│ │ Management │ │ Registry │ │ System │ │
│ └──────────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Workflow Runtime Layer │
│ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Workflow │ │ Bookmark │ │ Workflow │ │
│ │ Execution │ │ Manager │ │ Dispatcher │ │
│ │ Engine │ │ │ │ │ │
│ └──────────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ Persistence Layer │
│ ┌──────────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Workflow │ │ Activity │ │ Execution │ │
│ │ Definitions │ │ Execution │ │ Logs │ │
│ │ & Instances │ │ Records │ │ │ │
│ └──────────────────┘ └──────────────┘ └──────────────┘ │
│ (EF Core / MongoDB) │
└─────────────────────────────────────────────────────────────┘Major Components
1. Elsa Server
The Elsa Server is an ASP.NET Core application that hosts the workflow execution engine and exposes REST APIs for workflow management and execution.
Key Responsibilities:
Workflow Execution: Runs workflow instances using the workflow runtime
API Endpoints: Exposes REST APIs for managing workflows, activities, and workflow instances
Trigger Processing: Listens for and processes workflow triggers (HTTP, Timer, Events, etc.)
Background Services: Manages scheduled tasks, long-running workflows, and delayed activities
Authentication & Authorization: Controls access to workflow management and execution
Key Packages:
Elsa.Workflows.Core- Core workflow engineElsa.Workflows.Runtime- Workflow runtime and execution servicesElsa.Workflows.Management- Workflow definition and instance managementElsa.Workflows.Api- REST API endpoints
Configuration Example:
builder.Services.AddElsa(elsa =>
{
// Configure workflow management
elsa.UseWorkflowManagement(management =>
management.UseEntityFrameworkCore());
// Configure workflow runtime
elsa.UseWorkflowRuntime(runtime =>
runtime.UseEntityFrameworkCore());
// Enable API endpoints
elsa.UseWorkflowsApi();
// Enable real-time updates
elsa.UseRealTimeWorkflows();
});2. Elsa Studio
Elsa Studio is a Blazor application built as a Razor Class Library (RCL) that provides a visual designer for creating and managing workflows through a browser-based interface. It can be hosted using any Blazor hosting model, including Blazor WebAssembly, Blazor Server, or embedded in other Blazor applications.
Key Features:
Visual Workflow Designer: Drag-and-drop interface for building workflows
Activity Configuration: Rich UI for configuring activity properties and expressions
Workflow Execution Monitoring: Real-time view of running workflows
Instance Management: Browse and manage workflow instances
Execution History: View detailed execution logs and activity traces
Architecture:
Blazor WebAssembly SPA hosted separately from the server
Communicates with Elsa Server via REST APIs
Receives real-time updates via SignalR connections
Modular plugin architecture for extensibility
Key Packages:
Elsa.Studio- Core studio infrastructureElsa.Studio.Core.BlazorWasm- Blazor WASM hostingElsa.Studio.Workflows- Workflow design moduleElsa.Api.Client- API client for server communication
3. Activities
Activities are the building blocks of workflows, representing discrete units of work that can be composed together to create complex processes.
Activity Types:
Control Flow Activities: Sequence, Flowchart, If, Switch, For, While, Fork
Data Activities: SetVariable, WriteLine, ReadLine
HTTP Activities: HttpEndpoint, WriteHttpResponse, SendHttpRequest
Blocking Activities: Event, Delay, Timer (create bookmarks)
Trigger Activities: HttpEndpoint, Timer, Cron (can start workflows)
Custom Activities: User-defined activities extending base classes
Activity Lifecycle:
┌──────────────┐
│ Scheduled │ ← Activity added to scheduler
└──────┬───────┘
│
▼
┌──────────────┐
│ Executing │ ← Activity.ExecuteAsync() called
└──────┬───────┘
│
▼
┌──────────────┐ Creates ┌──────────────┐
│ Suspending │──Bookmark──→ │ Suspended │
└──────┬───────┘ └──────────────┘
│ │
│ │ Resume with stimulus
│ ▼
│ ┌──────────────┐
│ │ Resuming │
│ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Completed │◄─────────────│ Completing │
└──────────────┘ └──────────────┘Activity Properties:
Inputs: Configurable properties that accept values or expressions
Outputs: Data produced by the activity for downstream consumption
Outcomes: Named execution paths (e.g., "Done", "True", "False")
Metadata: Descriptive information (display name, description, category, icon)
4. Workflows
Workflows in Elsa exist as both definitions (blueprints) and instances (executions).
Workflow Definitions
A Workflow Definition is a blueprint that describes:
The activities and their configuration
Connections between activities (execution flow)
Variables and their types
Input and output definitions
Trigger configurations
Storage:
Stored as JSON in the database
Can be versioned (multiple versions of the same workflow)
Support for draft and published states
Workflow Instances
A Workflow Instance represents an execution of a workflow definition:
Contains the current execution state
Stores variable values and execution history
Maintains bookmarks for suspended execution
Tracks correlation IDs for external system integration
Instance States:
Running- Currently executingSuspended- Paused, waiting for external event or conditionFinished- Completed successfullyFaulted- Terminated due to an errorCanceled- Manually or programmatically canceled
5. Persistence Layer
The Persistence Layer provides abstractions and implementations for storing workflow data.
Storage Providers:
Entity Framework Core: SQL Server, PostgreSQL, SQLite, MySQL
MongoDB: Document-based storage
Memory: In-memory storage for testing
Persisted Data:
Workflow Definition Store
Workflow blueprints, versions, metadata
Define workflow templates
Workflow Instance Store
Running/completed instances, state, variables
Track execution state
Trigger Store
Trigger definitions, bookmarks
Enable workflow activation
Activity Execution Store
Activity execution records
Audit and debugging
Execution Log Store
Detailed execution logs
Troubleshooting and monitoring
Configuration Example:
elsa.UseWorkflowManagement(management =>
{
management.UseEntityFrameworkCore(ef =>
ef.UsePostgreSql(connectionString));
});
elsa.UseWorkflowRuntime(runtime =>
{
runtime.UseEntityFrameworkCore(ef =>
ef.UsePostgreSql(connectionString));
});Execution Model
Understanding Elsa's execution model is critical for architects and integrators.
Workflow Execution Flow
┌─────────────────┐
│ Trigger Event │ (HTTP request, timer, external event)
└────────┬────────┘
│
▼
┌─────────────────┐
│ Trigger Matcher │ ← Finds workflows with matching triggers
└────────┬────────┘
│
▼
┌─────────────────┐
│ Create Instance │ ← New WorkflowInstance created
└────────┬────────┘
│
▼
┌─────────────────┐
│ Schedule Root │ ← Root activity added to scheduler
│ Activity │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Execute Burst │ ← Continuous execution until blocking
└────────┬────────┘
│
├─────────────┐
│ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Complete │ │ Suspended │ ← Bookmark created
└─────────────┘ └──────┬───────┘
│
│ External stimulus
▼
┌──────────────┐
│ Resume Burst │
└──────┬───────┘
│
▼
(Continue execution)Execute vs Dispatch
Elsa provides two primary mechanisms for starting workflows:
Execute
Synchronous, inline execution within the current context.
Characteristics:
Runs in the caller's context (same thread, transaction)
Blocks until completion or first suspension point
Returns workflow state immediately
Useful for short-lived workflows
Better for unit testing
Use Cases:
Workflows that complete in a single burst
Synchronous business logic
Testing scenarios
Code Example:
var workflowRunner = serviceProvider.GetRequiredService<IWorkflowRunner>();
var result = await workflowRunner.RunAsync(workflow);Dispatch
Asynchronous, background execution via the workflow runtime.
Characteristics:
Queues workflow for background execution
Returns immediately without waiting for completion
Executes via mediator/message queue
Supports distributed processing
Better for long-running workflows
Use Cases:
Long-running workflows with delays or external events
High-throughput scenarios
Distributed systems
Fire-and-forget execution
Code Example:
var workflowDispatcher = serviceProvider.GetRequiredService<IWorkflowDispatcher>();
await workflowDispatcher.DispatchAsync(new DispatchWorkflowDefinitionRequest
{
DefinitionId = "my-workflow"
});Comparison Table:
Execution Mode
Synchronous
Asynchronous
Context
Caller's thread
Background worker
Response
Waits for completion
Immediate return
Distribution
Single process
Can be distributed
Performance
Lower overhead
Higher throughput
Use Case
Short workflows
Long-running workflows
Bookmarks, Triggers, and Stimuli
These three concepts work together to enable event-driven, long-running workflows.
Bookmarks
Suspension points that allow workflows to pause and resume later.
How They Work:
A blocking activity creates a bookmark with a unique payload
The workflow instance is suspended and persisted
Execution stops at that point
Later, an external event provides a matching stimulus
The bookmark is matched, and execution resumes
Example:
public class WaitForApprovalActivity : Activity
{
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
// Create a bookmark with a payload
context.CreateBookmark(
new Bookmark("approval-required",
payload: new { ApprovalId = "12345" }));
await Task.CompletedTask;
}
}Triggers
Entry points that can automatically start new workflow instances.
Characteristics:
Activities marked with
ActivityKind.TriggerIndexed when workflow definitions are published
Matched against incoming events/stimuli
Can start multiple instances (one per event)
Common Triggers:
HttpEndpoint- HTTP requests at specific pathsTimer- Time-based schedulesCron- Cron expressionsEvent- Custom application events
How Triggers Work:
1. Workflow published with HttpEndpoint trigger
2. Runtime indexes trigger: POST /api/orders
3. HTTP request arrives: POST /api/orders
4. Runtime matches trigger to workflow definition
5. New workflow instance created and started
6. Request data passed as workflow inputStimuli
External events that either start workflows (via triggers) or resume them (via bookmarks).
Types:
Workflow Stimuli: Start new instances (matched to triggers)
Bookmark Stimuli: Resume suspended instances (matched to bookmarks)
Flow:
External Event → Stimulus → Trigger Matcher → Start Workflow
↘ Bookmark Matcher → Resume WorkflowExample - Resume via Stimulus:
var workflowRuntime = serviceProvider.GetRequiredService<IWorkflowRuntime>();
// Resume a workflow waiting for approval
await workflowRuntime.ResumeWorkflowAsync(
workflowInstanceId,
bookmark: new Bookmark("approval-required"),
input: new { Approved = true });Workflow Execution Internals
Activity Execution Pipeline
Activities execute through a configurable middleware pipeline:
Request → [Middleware 1] → [Middleware 2] → [Activity] → Response
↓ ↓ ↓
Logging Validation ExecutionBuilt-in Middleware:
Exception handling
Logging
Activity execution tracking
State persistence
Fault tolerance
Workflow Scheduler
The scheduler manages activity execution order:
Scheduling Strategies:
Sequential: Activities execute one at a time
Parallel: Multiple activities execute concurrently (Fork activity)
Scheduler Operations:
Add activities to the work queue
Dequeue next activity
Execute via activity pipeline
Handle outcomes (schedule next activities)
Repeat until queue is empty or suspended
State Management
Workflow state is managed through:
Workflow Execution Context:
Contains current execution state
Manages variables and their values
Tracks activity execution history
Maintains scheduler state
Persistence Strategy:
State captured after each burst of execution
Serialized to JSON
Stored in database
Restored on resume
Data Flow
Understanding how data flows through the system is essential for integration.
Request Flow (HTTP Trigger Example)
1. HTTP Request
↓
2. ASP.NET Core Middleware (app.UseWorkflows())
↓
3. Trigger Matcher → Finds workflows with matching HttpEndpoint
↓
4. Workflow Dispatcher → Queues workflow execution
↓
5. Workflow Runtime → Picks up message
↓
6. Workflow Execution Engine
↓
7. Activity Execution (HttpEndpoint)
│ - Captures request data
│ - Sets workflow variables
↓
8. Subsequent Activities
│ - Access request data via variables
│ - Process business logic
↓
9. WriteHttpResponse Activity
│ - Generates HTTP response
↓
10. HTTP Response sent to clientVariable and Output Flow
┌──────────────┐
│ Activity A │ Produces output → Stored in execution context
└──────┬───────┘
│
▼ Output bound to variable
┌──────────────┐
│ Variable │ Available to downstream activities
└──────┬───────┘
│
▼ Variable accessed
┌──────────────┐
│ Activity B │ Consumes variable as input
└──────────────┘Example:
var queryStringsVar = builder.WithVariable<IDictionary<string, object>>();
var messageVar = builder.WithVariable<string>();
builder.Root = new Sequence
{
Activities =
{
// Activity A: Capture HTTP query strings
new HttpEndpoint
{
Path = new("/hello"),
QueryStringData = new(queryStringsVar)
},
// Transform data
new SetVariable
{
Variable = messageVar,
Value = new(context =>
queryStringsVar.Get(context)!["message"].ToString())
},
// Activity B: Use the variable
new WriteHttpResponse
{
Content = new(messageVar)
}
}
};Scalability and Performance
Elsa is designed for high performance and horizontal scalability.
Performance Characteristics
Execution Speed:
In-memory activities: ~1-5ms per activity
Persistence overhead: ~10-50ms per burst
HTTP activities: Depends on external service latency
Throughput:
Single instance: 100-1000+ workflows/second (depends on workflow complexity)
Clustered: Linear scaling with additional nodes
Memory:
Base process: ~100-200MB
Per active workflow: ~1-5KB (suspended workflows minimal overhead)
Workflow definitions cached in memory
Horizontal Scaling
Elsa supports running multiple server instances for high availability and scalability.
Required Configuration for Clustering:
Distributed Runtime
elsa.UseWorkflowRuntime(runtime =>
{
runtime.UseDistributedRuntime();
});Distributed Locking
runtime.DistributedLockProvider = sp =>
new PostgresDistributedSynchronizationProvider(connectionString);Distributed Caching
elsa.UseDistributedCache(cache =>
{
cache.UseMassTransit();
});
elsa.UseMassTransit(mt =>
{
mt.UseRabbitMq(rabbitMqConnectionString);
});Quartz.NET Clustering (if using Quartz scheduler)
elsa.UseQuartz(quartz =>
{
quartz.UsePostgreSql(connectionString);
});Scaling Strategies:
Vertical Scaling
Increase CPU/memory, worker count
Initial scaling, cost-effective
Horizontal Scaling
Add more server instances
High throughput, high availability
Read Replicas
Separate read/write databases
Read-heavy workloads
Partitioning
Route workflows to specific nodes
Tenant isolation, resource management
Optimization Tips
Reduce Persistence Overhead
Disable logging for high-frequency activities
Use in-memory storage for transient workflows
Batch database operations
Optimize Activity Design
Keep activities lightweight
Avoid blocking I/O in synchronous activities
Use async patterns for I/O operations
Worker Configuration
builder.Services.Configure<MediatorOptions>(opt =>
{
opt.CommandWorkerCount = 16;
opt.JobWorkerCount = 16;
opt.NotificationWorkerCount = 16;
});Database Optimization
Add appropriate indexes
Regular maintenance (vacuum, statistics)
Connection pooling
Cache Workflow Definitions
Enabled by default
Reduces database queries
Invalidated on changes via distributed cache
Extensibility Points
Elsa provides numerous extension points for customization.
1. Custom Activities
Create domain-specific activities by implementing IActivity or inheriting from base classes:
[Activity("MyCategory", "My Activity", "Does something custom")]
public class MyCustomActivity : CodeActivity
{
[Input(Description = "The input value")]
public Input<string> InputValue { get; set; } = default!;
[Output(Description = "The output value")]
public Output<string> OutputValue { get; set; } = default!;
protected override async ValueTask ExecuteAsync(
ActivityExecutionContext context)
{
var input = context.Get(InputValue);
var result = await Task.FromResult(input.ToUpper());
context.Set(OutputValue, result);
}
}2. Custom Triggers
Implement custom trigger activities for event-driven workflows:
[Activity("MyCategory", "My Trigger", Kind = ActivityKind.Trigger)]
public class MyTriggerActivity : Activity<object>
{
protected override async ValueTask ExecuteAsync(
ActivityExecutionContext context)
{
if (!context.IsTriggerOfWorkflow())
{
// Create bookmark for existing instances
context.CreateBookmark();
return;
}
// Trigger mode: complete immediately
await context.CompleteActivityAsync();
}
protected override object GetTriggerPayload(
TriggerIndexingContext context)
{
return new { EventType = "MyEvent" };
}
}3. Custom Middleware
Add custom behavior to workflow or activity execution:
public class MyActivityMiddleware : IActivityExecutionMiddleware
{
public async ValueTask ExecuteAsync(
ActivityExecutionContext context,
ActivityExecutionDelegate next)
{
// Before execution
Console.WriteLine($"Executing {context.Activity.Type}");
await next(context);
// After execution
Console.WriteLine($"Completed {context.Activity.Type}");
}
}
// Register middleware
elsa.UseWorkflowRuntime(runtime =>
{
runtime.UseDefaultActivityExecutionPipeline(pipeline =>
{
pipeline.UseMiddleware<MyActivityMiddleware>();
});
});4. Custom Persistence Providers
Implement custom storage backends:
public class MyWorkflowInstanceStore : IWorkflowInstanceStore
{
public Task SaveAsync(WorkflowInstance instance,
CancellationToken cancellationToken) { ... }
public Task<WorkflowInstance?> FindAsync(
WorkflowInstanceFilter filter,
CancellationToken cancellationToken) { ... }
// ... other interface methods
}5. Custom Expression Evaluators
Add support for custom expression languages:
public class MyExpressionHandler : IExpressionHandler
{
public string Language => "mylang";
public Task<object?> EvaluateAsync(
Expression expression,
Type returnType,
ExpressionExecutionContext context,
CancellationToken cancellationToken) { ... }
}6. Studio Extensibility
Custom UI Hints: Define how activity properties appear in the designer:
[Input(UIHint = InputUIHints.Dropdown)]
public Input<string> Status { get; set; } = default!;Custom Activity Providers: Dynamically generate activities from external sources:
public class ApiActivityProvider : IActivityProvider
{
public async ValueTask<IEnumerable<ActivityDescriptor>>
GetDescriptorsAsync(CancellationToken cancellationToken)
{
// Fetch activity definitions from API
var activities = await _apiClient.GetActivitiesAsync();
// Convert to ActivityDescriptor
return activities.Select(a => new ActivityDescriptor
{
TypeName = a.Type,
DisplayName = a.Name,
// ... other properties
});
}
}Deployment Topologies
Elsa supports various deployment configurations to meet different requirements.
1. All-in-One (Development)
Single server hosting both Elsa Server and Studio.
┌─────────────────────────┐
│ Single Server │
│ ┌──────────────────┐ │
│ │ Elsa Server │ │
│ │ + Studio WASM │ │
│ └──────────────────┘ │
│ ┌──────────────────┐ │
│ │ Database │ │
│ └──────────────────┘ │
└─────────────────────────┘Pros: Simple setup, minimal infrastructure Cons: Not scalable, single point of failure Use Case: Development, POC, small deployments
2. Separate Server and Studio (Recommended)
Elsa Server and Studio deployed as separate applications.
┌─────────────────┐ ┌─────────────────┐
│ Elsa Studio │──────▶│ Elsa Server │
│ (Blazor WASM) │ API │ (ASP.NET Core) │
└─────────────────┘ └────────┬────────┘
│
┌────────▼────────┐
│ Database │
└─────────────────┘Pros: Independent scaling, separate concerns Cons: More complex deployment Use Case: Production deployments
3. Multi-Instance Cluster (High Availability)
Multiple Elsa Server instances behind a load balancer.
┌─────────────────┐ ┌─────────────────┐
│ Elsa Studio │ │ Load Balancer │
└────────┬────────┘ └────────┬────────┘
│ │
│ ┌────────────────┼────────────────┐
│ │ │ │
│ ┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
└──▶│ Server 1 │ │ Server 2 │ │ Server N │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└───────┬───────┴───────┬───────┘
│ │
┌──────▼──────┐ ┌─────▼──────┐
│ Database │ │ Redis/ │
│ (Primary) │ │ RabbitMQ │
└─────────────┘ └────────────┘Requirements:
Shared database
Distributed locking (PostgreSQL, Redis)
Distributed caching (MassTransit + RabbitMQ/Azure Service Bus)
Quartz.NET clustering (if using Quartz)
Pros: High availability, horizontal scaling Cons: Complex configuration, infrastructure overhead Use Case: Production, high-traffic environments
4. Kubernetes Deployment
Containerized deployment with orchestration.
┌────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Ingress Controller │ │
│ └────────┬─────────────────────┬────────────────┘ │
│ │ │ │
│ ┌────────▼────────┐ ┌───────▼──────────┐ │
│ │ Studio Pod(s) │ │ Server Pod(s) │ │
│ │ (Deployment) │ │ (Deployment) │ │
│ └─────────────────┘ └───────┬──────────┘ │
│ │ │
│ ┌──────────────────────────────▼────────────┐ │
│ │ External Services │ │
│ │ - PostgreSQL (StatefulSet/External) │ │
│ │ - RabbitMQ (StatefulSet/External) │ │
│ │ - Redis (StatefulSet/External) │ │
│ └─────────────────────────────────────────┘ │
└────────────────────────────────────────────────────┘Key Considerations:
Use persistent volumes for workflow state
Configure health checks and readiness probes
Set up horizontal pod autoscaling
Use ConfigMaps and Secrets for configuration
Enable distributed runtime features
Example Deployment YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: elsa-server
spec:
replicas: 3
selector:
matchLabels:
app: elsa-server
template:
metadata:
labels:
app: elsa-server
spec:
containers:
- name: elsa-server
image: myregistry/elsa-server:latest
ports:
- containerPort: 80
env:
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef:
name: elsa-secrets
key: database-connection
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 10
periodSeconds: 55. Microservices Architecture
Separate workflow domain into multiple services.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Order │ │ Inventory │ │ Shipping │
│ Workflows │ │ Workflows │ │ Workflows │
│ Service │ │ Service │ │ Service │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└──────────┬───────┴───────┬──────────┘
│ │
┌───────▼──────┐ ┌─────▼──────┐
│ Message Bus │ │ Database │
│ (RabbitMQ) │ │ (Per Svc) │
└──────────────┘ └────────────┘Use Case: Domain-driven design, team autonomy Pros: Independent deployment, domain isolation Cons: Complexity, distributed transactions
Multi-Tenancy Architecture
Elsa supports multi-tenancy for SaaS applications.
Tenant Isolation Strategies
1. Shared Database, Shared Schema
All tenants share the same database and tables
Tenant ID column on all tables
Row-level security via application logic
2. Shared Database, Separate Schemas
Each tenant gets their own schema
Better isolation than shared schema
More complex migration management
3. Separate Databases
Complete database isolation per tenant
Best security and isolation
Higher resource overhead
Multi-Tenant Configuration
builder.Services.AddElsa(elsa =>
{
elsa.UseMultitenancy(multitenancy =>
{
multitenancy.UseEntityFrameworkCoreStore();
});
elsa.UseWorkflowManagement(management =>
{
management.UseEntityFrameworkCore(ef =>
{
ef.UseMultitenancy();
});
});
});Tenant Resolution
Tenants are resolved via:
HTTP headers
Subdomain
URL path
Claims in JWT token
Custom resolution strategy
public class CustomTenantResolver : ITenantResolver
{
public Task<TenantResolverResult> ResolveAsync(
TenantResolverContext context)
{
var httpContext = context.HttpContext;
var tenantId = httpContext.Request.Headers["X-Tenant-Id"];
return Task.FromResult(new TenantResolverResult
{
TenantId = tenantId
});
}
}Security Considerations
Authentication & Authorization
Server Authentication:
API Key authentication for machine-to-machine
JWT bearer tokens for user authentication
OIDC integration (Azure AD, Auth0, IdentityServer)
Studio Authentication:
Login module with username/password
OIDC integration
Custom authentication providers
Configuration:
elsa.UseIdentity(identity =>
{
identity.TokenOptions = options =>
options.SigningKey = builder.Configuration["Jwt:SigningKey"]; // Use secure key storage in production
identity.UseAdminUserProvider();
});
elsa.UseDefaultAuthentication(auth =>
auth.UseAdminApiKey());Workflow Security
HTTP Endpoint Authorization:
new HttpEndpoint
{
Path = new("/secure-endpoint"),
AuthorizeWithPolicy = new("AdminOnly"),
CanStartWorkflow = true
}Variable and Input Validation:
Validate all external inputs
Sanitize data before persistence
Use typed variables to prevent injection
Monitoring and Observability
Health Checks
builder.Services.AddHealthChecks()
.AddDbContextCheck<WorkflowDbContext>()
.AddRabbitMQ(rabbitMqConnectionString);
app.MapHealthChecks("/health");Logging
Elsa uses structured logging via ILogger:
builder.Services.AddElsa(elsa =>
{
elsa.UseWorkflowRuntime(runtime =>
{
runtime.LogPersistenceMode = LogPersistenceMode.Inherit;
});
});Log Levels:
Activity execution logs
Workflow lifecycle events
Exception details
Performance metrics
Execution Logs
Activity execution is logged to the database for auditing:
var executionLogs = await workflowExecutionLogStore.FindAsync(
new WorkflowExecutionLogRecordFilter
{
WorkflowInstanceId = instanceId
});Metrics and Telemetry
Key Metrics:
Workflows started/completed per second
Average workflow execution time
Active workflow instances
Suspended workflow count
Activity failure rate
Integration:
Application Insights
Prometheus
OpenTelemetry
Best Practices
Design Patterns
Idempotency: Design workflows to handle duplicate executions
Compensation: Implement compensating actions for rollback scenarios
Saga Pattern: Use for distributed transactions across services
State Machine: Use StateMachine activities for complex state transitions
Performance
Minimize Persistence: Disable logging for high-frequency activities
Batch Operations: Group related activities to reduce overhead
Async Activities: Use async patterns for I/O operations
Connection Pooling: Configure database connection pools appropriately
Reliability
Error Handling: Use Fault activity and exception handling
Retry Logic: Implement retry policies for transient failures
Timeouts: Set appropriate timeouts for external calls
Health Checks: Monitor system health continuously
Maintenance
Versioning: Version workflow definitions for safe updates
Migration: Plan for workflow instance migration when updating definitions
Archival: Archive completed workflows periodically
Monitoring: Set up alerts for failures and performance degradation
Reference Sources
This architecture guide is based on the following official Elsa sources:
Elsa Core Repository:
Core workflow engine implementation
Activity library and runtime services
Persistence layer abstractions
Elsa Studio Repository:
Blazor WebAssembly designer application
Studio modules and extensibility
Official Documentation:
Next Steps
For Architects: Review Distributed Hosting for production deployment
For Backend Integrators: Learn to create Custom Activities
For Platform/DevOps: Explore Container Deployment options
For Workflow Designers: Start with Hello World tutorial
Summary
Elsa Workflows provides a flexible, scalable architecture for building workflow-driven applications. Key takeaways:
Modular Design: Separate concerns across layers (presentation, application, runtime, persistence)
Extensible: Custom activities, triggers, middleware, and persistence providers
Scalable: Horizontal scaling with distributed runtime and locking
Event-Driven: Triggers, bookmarks, and stimuli enable long-running workflows
Flexible Deployment: From single-server to Kubernetes clusters
Production-Ready: Multi-tenancy, security, monitoring, and high availability
By understanding these architectural principles, you can design and deploy robust workflow solutions with Elsa.
Last updated