Introduce TenantDeleted event to handle tenant cleanup (#6843)
* Introduce `TenantDeleted` event to handle tenant cleanup Added a `TenantDeleted` event to differentiate between tenant deactivation and deletion. Updated event handlers and services to support unregistering resources only during tenant deletion, ensuring clearer separation of responsibilities. Included ADR documentation for the new event. * Remove unnecessary blank line in tenant deactivation logic
This commit is contained in:
parent
fc5612687d
commit
133e8dfc63
1
Elsa.sln
1
Elsa.sln
|
|
@ -392,6 +392,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "adr", "adr", "{0A04B1FD-06C
|
|||
doc\adr\graph.dot = doc\adr\graph.dot
|
||||
doc\adr\0003-direct-bookmark-management-in-workflowexecutioncontext.md = doc\adr\0003-direct-bookmark-management-in-workflowexecutioncontext.md
|
||||
doc\adr\0004-activity-execution-snapshots.md = doc\adr\0004-activity-execution-snapshots.md
|
||||
doc\adr\0005-tenant-deleted-event.md = doc\adr\0005-tenant-deleted-event.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bounty", "bounty", "{9B80A705-2E31-4012-964A-83963DCDB384}"
|
||||
|
|
|
|||
23
doc/adr/0005-tenant-deleted-event.md
Normal file
23
doc/adr/0005-tenant-deleted-event.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# 5. Tenant Deleted Event
|
||||
|
||||
Date: 2025-08-05
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
As outlined in issue [#6661](https://github.com/elsa-workflows/elsa-core/issues/6661), there is a need to differentiate between **Tenant Deactivating** and **Tenant Deleting** events.
|
||||
|
||||
Currently, the `TenantDeactivated` event is used to unregister timer-based triggers. However, this causes the triggers to be unregistered when the application host shuts down, which is not the desired behavior. Instead, we want the triggers to remain registered until the tenant is explicitly deleted.
|
||||
|
||||
## Decision
|
||||
|
||||
To address this, we will introduce a new event called `TenantDeleted`. This event will be raised when a tenant is deleted and will be responsible for unregistering timer-based triggers. This ensures that the triggers remain active until the tenant is explicitly deleted.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Timer-based triggers will no longer be unregistered during tenant deactivation. Instead, they will remain active until the tenant is deleted.
|
||||
- The `TenantDeactivated` event will continue to be used for deactivating tenants without affecting the registration of timer-based triggers.
|
||||
- The new `TenantDeleted` event will specifically handle the cleanup of resources associated with a tenant when it is deleted, ensuring a clear separation of responsibilities.
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
digraph {
|
||||
node [shape = plaintext];
|
||||
node [shape=plaintext];
|
||||
subgraph {
|
||||
_1 [label = "1. Record architecture decisions"; URL = "0001-record-architecture-decisions.html"];
|
||||
_2 [label = "2. Fault Propagation from Child to Parent Activities"; URL ="0002-fault-propagation-from-child-to-parent-activities.html"];
|
||||
_1 -> _2 [style= "dotted", weight = 1];
|
||||
_3 [label = "3. Direct Bookmark Management in WorkflowExecutionContext"; URL = "0003-direct-bookmark-management-in-workflowexecutioncontext.html"];
|
||||
_2 -> _3 [style = "dotted", weight = 1];
|
||||
_4 [label ="4. Activity Execution Snapshots"; URL = "0004-activity-execution-snapshots.html"];
|
||||
_3 -> _4 [style = "dotted", weight = 1];
|
||||
_1 [label="1. Record architecture decisions"; URL="0001-record-architecture-decisions.html"];
|
||||
_2 [label="2. Fault Propagation from Child to Parent Activities"; URL="0002-fault-propagation-from-child-to-parent-activities.html"];
|
||||
_1 -> _2 [style="dotted", weight=1];
|
||||
_3 [label="3. Direct Bookmark Management in WorkflowExecutionContext"; URL="0003-direct-bookmark-management-in-workflowexecutioncontext.html"];
|
||||
_2 -> _3 [style="dotted", weight=1];
|
||||
_4 [label="4. Activity Execution Snapshots"; URL="0004-activity-execution-snapshots.html"];
|
||||
_3 -> _4 [style="dotted", weight=1];
|
||||
_5 [label="5. Tenant Deleted Event"; URL="0005-tenant-deleted-event.html"];
|
||||
_4 -> _5 [style="dotted", weight=1];
|
||||
}
|
||||
}
|
||||
|
|
@ -3,4 +3,5 @@
|
|||
* [1. Record architecture decisions](0001-record-architecture-decisions.md)
|
||||
* [2. Fault Propagation from Child to Parent Activities](0002-fault-propagation-from-child-to-parent-activities.md)
|
||||
* [3. Direct Bookmark Management in WorkflowExecutionContext](0003-direct-bookmark-management-in-workflowexecutioncontext.md)
|
||||
* [4. Activity Execution Snapshots](0004-activity-execution-snapshots.md)
|
||||
* [4. Activity Execution Snapshots](0004-activity-execution-snapshots.md)
|
||||
* [5. Tenant Deleted Event](0005-tenant-deleted-event.md)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Elsa.Common.Multitenancy;
|
||||
|
||||
public interface ITenantDeletedEvent
|
||||
{
|
||||
Task TenantDeletedAsync(TenantDeletedEventArgs args);
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
namespace Elsa.Common.Multitenancy;
|
||||
|
||||
public record TenantDeletedEventArgs(Tenant Tenant, TenantScope TenantScope, CancellationToken CancellationToken) : TenantEventArgs(Tenant, TenantScope, CancellationToken);
|
||||
|
|
@ -64,7 +64,7 @@ public class DefaultTenantService(IServiceScopeFactory scopeFactory, ITenantScop
|
|||
var tenants = dictionary.Values.ToArray();
|
||||
|
||||
foreach (var tenant in tenants)
|
||||
await UnregisterTenantAsync(tenant, cancellationToken);
|
||||
await UnregisterTenantAsync(tenant, false, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task RefreshAsync(CancellationToken cancellationToken = default)
|
||||
|
|
@ -85,7 +85,7 @@ public class DefaultTenantService(IServiceScopeFactory scopeFactory, ITenantScop
|
|||
foreach (var removedTenantId in removedTenantIds)
|
||||
{
|
||||
var removedTenant = currentTenants[removedTenantId];
|
||||
await UnregisterTenantAsync(removedTenant, cancellationToken);
|
||||
await UnregisterTenantAsync(removedTenant, true, cancellationToken);
|
||||
}
|
||||
|
||||
foreach (var addedTenantId in addedTenantIds)
|
||||
|
|
@ -137,14 +137,19 @@ public class DefaultTenantService(IServiceScopeFactory scopeFactory, ITenantScop
|
|||
await tenantEvents.TenantActivatedAsync(new(tenant, scope, cancellationToken));
|
||||
}
|
||||
|
||||
private async Task UnregisterTenantAsync(Tenant tenant, CancellationToken cancellationToken = default)
|
||||
private async Task UnregisterTenantAsync(Tenant tenant, bool isDeleted, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_tenantScopesDictionary!.Remove(tenant, out var scope))
|
||||
{
|
||||
_tenantsDictionary!.Remove(tenant.Id.EmptyIfNull(), out _);
|
||||
|
||||
using (tenantAccessor.PushContext(tenant))
|
||||
{
|
||||
await tenantEvents.TenantDeactivatedAsync(new(tenant, scope, cancellationToken));
|
||||
|
||||
if (isDeleted)
|
||||
await tenantEvents.TenantDeletedAsync(new(tenant, scope, cancellationToken));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,34 +2,54 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace Elsa.Common.Multitenancy;
|
||||
|
||||
public class TenantEventsManager(IEnumerable<ITenantActivatedEvent> tenantActivatedEvents, IEnumerable<ITenantDeactivatedEvent> tenantDeactivatedEvents, ILogger<TenantEventsManager> logger)
|
||||
public class TenantEventsManager(
|
||||
IEnumerable<ITenantActivatedEvent> tenantActivatedEvents,
|
||||
IEnumerable<ITenantDeactivatedEvent> tenantDeactivatedEvents,
|
||||
IEnumerable<ITenantDeletedEvent> tenantDeletedEvents,
|
||||
ILogger<TenantEventsManager> logger)
|
||||
{
|
||||
public async Task TenantActivatedAsync(TenantActivatedEventArgs args)
|
||||
{
|
||||
foreach (var tenantActivatedEvent in tenantActivatedEvents)
|
||||
{
|
||||
try
|
||||
{
|
||||
await tenantActivatedEvent.TenantActivatedAsync(args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Error occurred while processing tenant activated event.");
|
||||
}
|
||||
}
|
||||
await ExecuteEventHandlersAsync(
|
||||
tenantActivatedEvents,
|
||||
(handler, eventArgs) => handler.TenantActivatedAsync(eventArgs),
|
||||
args,
|
||||
"activated");
|
||||
}
|
||||
|
||||
public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args)
|
||||
{
|
||||
foreach (var tenantDeactivatedEvent in tenantDeactivatedEvents)
|
||||
await ExecuteEventHandlersAsync(
|
||||
tenantDeactivatedEvents,
|
||||
(handler, eventArgs) => handler.TenantDeactivatedAsync(eventArgs),
|
||||
args,
|
||||
"deactivated");
|
||||
}
|
||||
|
||||
public async Task TenantDeletedAsync(TenantDeletedEventArgs args)
|
||||
{
|
||||
await ExecuteEventHandlersAsync(
|
||||
tenantDeletedEvents,
|
||||
(handler, eventArgs) => handler.TenantDeletedAsync(eventArgs),
|
||||
args,
|
||||
"deleted");
|
||||
}
|
||||
|
||||
private async Task ExecuteEventHandlersAsync<THandler, TArgs>(
|
||||
IEnumerable<THandler> handlers,
|
||||
Func<THandler, TArgs, Task> handlerAction,
|
||||
TArgs args,
|
||||
string eventType)
|
||||
{
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
try
|
||||
{
|
||||
await tenantDeactivatedEvent.TenantDeactivatedAsync(args);
|
||||
await handlerAction(handler, args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "Error occurred while processing tenant deactivated event.");
|
||||
logger.LogError(e, "Error occurred while processing tenant {EventType} event.", eventType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public class SchedulingFeature : FeatureBase
|
|||
Services
|
||||
.AddSingleton<UpdateTenantSchedules>()
|
||||
.AddSingleton<ITenantActivatedEvent>(sp => sp.GetRequiredService<UpdateTenantSchedules>())
|
||||
.AddSingleton<ITenantDeactivatedEvent>(sp => sp.GetRequiredService<UpdateTenantSchedules>())
|
||||
.AddSingleton<ITenantDeletedEvent>(sp => sp.GetRequiredService<UpdateTenantSchedules>())
|
||||
.AddSingleton<IScheduler, LocalScheduler>()
|
||||
.AddSingleton<CronosCronParser>()
|
||||
.AddSingleton(CronParser)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ using Timer = Elsa.Scheduling.Activities.Timer;
|
|||
|
||||
namespace Elsa.Scheduling.Handlers;
|
||||
|
||||
public class UpdateTenantSchedules : ITenantActivatedEvent, ITenantDeactivatedEvent
|
||||
public class UpdateTenantSchedules : ITenantActivatedEvent, ITenantDeletedEvent
|
||||
{
|
||||
private static readonly string[] ActivityTypeNames =
|
||||
[
|
||||
|
|
@ -31,7 +31,7 @@ public class UpdateTenantSchedules : ITenantActivatedEvent, ITenantDeactivatedEv
|
|||
await bookmarkScheduler.ScheduleAsync(bookmarks, args.CancellationToken);
|
||||
}
|
||||
|
||||
public async Task TenantDeactivatedAsync(TenantDeactivatedEventArgs args)
|
||||
public async Task TenantDeletedAsync(TenantDeletedEventArgs args)
|
||||
{
|
||||
var serviceProvider = args.TenantScope.ServiceProvider;
|
||||
var cancellationToken = args.CancellationToken;
|
||||
|
|
|
|||
Loading…
Reference in a new issue