Soft Delete Example
Overview
This document demonstrates the complete Soft Delete flow using the SoftDeleteShipping feature from the EBusiness application. The flow starts with a DELETE request to the controller and ends with event publishing and subscriber processing. Soft delete marks the entity as deleted without physically removing it from the database.
Complete Flow Architecture
DELETE Request β Controller β Service β PreBus Plugins β Command Handler β RESTClient β External System
β
Response β Controller β Service β Command Handler β RESTClient β External System
Detailed Flow Breakdown
1. DELETE Request
β
2. Controller (API Entry Point)
β
3. Service Layer (Business Orchestration)
β
4. PreBus Processing (Validation Pipeline)
βββ SoftDeleteShippingSequence (Plugin Registration)
βββ SoftDeleteShippingDataPacket (Validation Context)
βββ IsValidShippingForSoftDelete Plugin (Business Rules)
β
5. Command Handler (RESTClient Integration)
βββ Internal DTO β Request DTO Mapping
βββ RESTClient Call to External System
βββ Response DTO Processing
β
6. Event Publishing (Asynchronous Processing)
β
7. Subscribers (Side Effects)
Step-by-Step Implementation
1. API Controller - The Entry Point
File: ShippingController_SoftDeleteShipping.cs
[HttpDelete()]
[Route("SoftDeleteShipping/{id}")]
[ProducesResponseType(typeof(BadRequestResult), 400)]
[ProducesResponseType(typeof(string), 200)]
public async Task<IActionResult> SoftDeleteShipping(string id)
{
SoftDeleteShippingDto dto = new SoftDeleteShippingDto();
dto.Id = id;
return await RunService(200, dto, _processShippingService.SoftDeleteShipping);
}
What Happens:
HTTP Method:
DELETE /api/Shipping/SoftDeleteShipping/{id}
Input: Shipping ID from URL parameter
DTO Creation: Creates
SoftDeleteShippingDto
with the IDAction: Calls the service layer to process the soft delete
Response: HTTP 200 OK with success status
2. Service Layer - Business Orchestration
File: ProcessShippingService_SoftDeleteShipping.cs
public async Task<CommandResult> SoftDeleteShipping(SoftDeleteShippingDto dto)
{
var packet = await ProcessBusinessRuleSequence<SoftDeleteShippingDataPacket, SoftDeleteShippingSequence, SoftDeleteShippingDto, FlexAppContextBridge>(dto);
if (packet.HasError)
{
return new CommandResult(Status.Failed, packet.Errors());
}
else
{
dto.SetGeneratedId(_pkGenerator.GenerateKey());
SoftDeleteShippingCommand cmd = new SoftDeleteShippingCommand
{
Dto = dto,
};
await ProcessCommand(cmd);
CommandResult cmdResult = new CommandResult(Status.Success);
SoftDeleteShippingResultModel outputResult = new SoftDeleteShippingResultModel();
cmdResult.result = outputResult;
return cmdResult;
}
}
What Happens:
PreBus Processing: Executes business rule sequences (plugins)
Validation: Processes business rule sequences
ID Generation: Generates unique key for tracking
Command Creation: Creates
SoftDeleteShippingCommand
with DTOCommand Processing: Calls the command handler
Result: Returns success status
2.1. PreBus Business Rule Sequence - Validation Pipeline
File: SoftDeleteShippingSequence.cs
public class SoftDeleteShippingSequence : FlexiBusinessRuleSequenceBase<SoftDeleteShippingDataPacket>
{
public SoftDeleteShippingSequence()
{
this.Add<IsValidShippingForSoftDelete>();
}
}
What Happens:
Plugin Registration: Registers validation plugins in execution order
Sequential Processing: Executes plugins one by one
Error Collection: Collects validation errors from all plugins
Early Exit: Stops processing if any plugin fails
2.2. PreBus Data Packet - Validation Context
File: SoftDeleteShippingDataPacket.cs
public partial class SoftDeleteShippingDataPacket : FlexiFlowDataPacketWithDtoBridge<SoftDeleteShippingDto, FlexAppContextBridge>
{
protected readonly ILogger<SoftDeleteShippingDataPacket> _logger;
public SoftDeleteShippingDataPacket(ILogger<SoftDeleteShippingDataPacket> logger)
{
_logger = logger;
}
#region "Properties
//Models and other properties goes here
#endregion
}
What Happens:
Context Container: Holds DTO and application context
Error Collection: Collects validation errors from plugins
Data Sharing: Allows plugins to share data during validation
Logging: Provides logging capabilities for plugins
2.3. PreBus Validation Plugin - Business Rules
File: IsValidShippingForSoftDelete.cs
public partial class IsValidShippingForSoftDelete : FlexiBusinessRuleBase, IFlexiBusinessRule<SoftDeleteShippingDataPacket>
{
public override string Id { get; set; } = "3a1cd5b1fa98b4c63378de9607706082";
public override string FriendlyName { get; set; } = "IsValidShippingForSoftDelete";
protected readonly ILogger<IsValidShippingForSoftDelete> _logger;
protected readonly RepoFactory _repoFactory;
public IsValidShippingForSoftDelete(ILogger<IsValidShippingForSoftDelete> logger, RepoFactory repoFactory)
{
_logger = logger;
_repoFactory = repoFactory;
}
public virtual async Task Validate(SoftDeleteShippingDataPacket packet)
{
//Uncomment the below line if validating against a db data using your repo
//_repoFactory.Init(packet.Dto);
//If any validation fails, uncomment and use the below line of code to add error to the packet
//packet.AddError("key", "ErrorMessage");
await Task.CompletedTask; //If you have any await in the validation, remove this line
}
}
What Happens:
Business Rule Validation: Implements specific validation logic
Database Access: Can access repository for data validation
Error Reporting: Adds errors to the data packet if validation fails
Async Support: Supports asynchronous validation operations
Dependency Injection: Receives logger and repository factory
3. Command Handler - RESTClient Integration
File: SoftDeleteShippingHandler.cs
public partial class SoftDeleteShippingHandler : ISoftDeleteShippingHandler
{
protected string EventCondition = "";
protected readonly ILogger<SoftDeleteShippingHandler> _logger;
protected readonly IFlexHost _flexHost;
protected ShippingRESTClient _restClient;
protected FlexAppContextBridge? _flexAppContext;
public SoftDeleteShippingHandler(ILogger<SoftDeleteShippingHandler> logger, IFlexHost flexHost, ShippingRESTClient restClient)
{
_logger = logger;
_flexHost = flexHost;
_restClient = restClient;
}
public virtual async Task Execute(SoftDeleteShippingCommand cmd, IFlexServiceBusContext serviceBusContext)
{
// Convert internal DTO to Request DTO
SoftDeleteShippingRequestDto requestParams = new SoftDeleteShippingRequestDto
{
Id = cmd.Dto.Id,
DeletionReason = cmd.Dto.DeletionReason ?? "User requested soft deletion",
SoftDeleteFlag = true
};
// Call external REST service
var response = await _restClient.SoftDeleteShipping(requestParams);
// Process response
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var responseDto = JsonConvert.DeserializeObject<SoftDeleteShippingResponseDto>(responseContent);
_logger.LogDebug("Shipping soft deleted successfully with external ID: {ExternalId}", responseDto?.Id);
// Set success condition for event firing
EventCondition = "CONDITION_ONSUCCESS";
}
else
{
_logger.LogError("Failed to soft delete shipping. Status: {StatusCode}", response.StatusCode);
EventCondition = "CONDITION_ONFAILED";
}
await this.Fire(EventCondition, serviceBusContext);
}
}
What Happens:
DTO Mapping: Converts internal DTO to Request DTO for external system
RESTClient Call: Calls external shipping service via RESTClient
Response Processing: Handles success/failure responses from external system
Logging: Logs success/failure of external service call
Event Publishing: Fires events based on external service result
4. Domain Model - Business Logic
File: Shipping/SoftDeleteShipping.cs
public virtual Shipping SoftDeleteShipping(SoftDeleteShippingCommand cmd)
{
Guard.AgainstNull("Shipping model cannot be empty", cmd);
this.Id = cmd.Dto.Id;
this.SetSoftDeleted();
//Set your appropriate SetSoftDeleted for the inner object here
return this;
}
What Happens:
Validation: Guards against null commands
ID Assignment: Sets the shipping ID from DTO
State Management: Marks entity as soft deleted
Child Objects: Processes child object soft deletions
5. NServiceBus Handler - Message Processing
File: SoftDeleteShippingNsbHandler.cs
public class SoftDeleteShippingNsbHandler : NsbCommandHandler<SoftDeleteShippingCommand>
{
readonly ILogger<SoftDeleteShippingNsbHandler> _logger;
readonly IFlexHost _flexHost;
readonly ISoftDeleteShippingHandler _handler;
public SoftDeleteShippingNsbHandler(ILogger<SoftDeleteShippingNsbHandler> logger, IFlexHost flexHost, ISoftDeleteShippingHandler handler)
{
_logger = logger;
_flexHost = flexHost;
_handler = handler;
}
public override async Task Handle(SoftDeleteShippingCommand message, IMessageHandlerContext context)
{
_logger.LogTrace($"Executing {nameof(SoftDeleteShippingNsbHandler)}");
await _handler.Execute(message, new NsbHandlerContextBridge(context));
}
}
What Happens:
Message Reception: Receives
SoftDeleteShippingCommand
from message busLogging: Logs handler execution
Delegation: Calls the actual command handler
Context Bridge: Converts NServiceBus context to FlexBase context
6. Event Publishing - Asynchronous Processing
Event: ShippingSoftDeletedEvent
(if implemented)
public class ShippingSoftDeletedEvent : FlexEventBridge<FlexAppContextBridge>
{
// Event data is automatically populated by FlexBase
}
What Happens:
Event Creation: FlexBase creates event with shipping data
Message Bus: Event is published to message bus
Subscriber Notification: All subscribers are notified
7. Event Subscribers - Side Effects
File: NotifyLogisticsOnShippingSoftDeleted.cs
(if implemented)
public partial class NotifyLogisticsOnShippingSoftDeleted : INotifyLogisticsOnShippingSoftDeleted
{
protected readonly ILogger<NotifyLogisticsOnShippingSoftDeleted> _logger;
protected string EventCondition = "";
public NotifyLogisticsOnShippingSoftDeleted(ILogger<NotifyLogisticsOnShippingSoftDeleted> logger)
{
_logger = logger;
}
public virtual async Task Execute(ShippingSoftDeletedEvent @event, IFlexServiceBusContext serviceBusContext)
{
_flexAppContext = @event.AppContext;
//TODO: Write your business logic here:
// - Update logistics systems
// - Notify carriers of cancellation
// - Update analytics
// - Archive related data
await this.Fire<NotifyLogisticsOnShippingSoftDeleted>(EventCondition, serviceBusContext);
}
}
What Happens:
Event Reception: Receives
ShippingSoftDeletedEvent
from message busSide Effects: Executes business logic (logistics updates, notifications, etc.)
Additional Events: Can fire more events if needed
Data Transfer Objects (DTOs)
Input DTO: SoftDeleteShippingDto
SoftDeleteShippingDto
public partial class SoftDeleteShippingDto : DtoBridge
{
public string Id { get; set; }
// Optional: Reason for soft delete
public string DeletionReason { get; set; }
}
Command: SoftDeleteShippingCommand
SoftDeleteShippingCommand
public class SoftDeleteShippingCommand : FlexCommandBridge<SoftDeleteShippingDto, FlexAppContextBridge>
{
// Command data is automatically populated by FlexBase
}
Key Differences from Hard Delete
Soft Delete vs Hard Delete Characteristics
Data Persistence
β Data remains in database
β Data physically removed
Recovery
β Can be restored
β Cannot be recovered
Audit Trail
β Complete history maintained
β History lost
Performance
β Fast (just flag update)
β Fast (row deletion)
Storage
β Uses more storage
β Frees storage
Queries
β Need to filter deleted items
β No filtering needed
Business Rules
β Can validate before delete
β Can validate before delete
Soft Delete-Specific Features
Data Preservation: Entity remains in database with deleted flag
Recovery: Can be restored if needed
Audit Trail: Maintains complete history
Query Filtering: Queries must filter out soft-deleted items
Business Logic: Can implement complex deletion rules
Soft Delete Pattern Benefits
Data Safety: No accidental data loss
Compliance: Meets regulatory requirements for data retention
Audit Trail: Complete history of all operations
Recovery: Can restore deleted data if needed
Business Rules: Can implement complex deletion logic
Flow Summary
Synchronous Flow (Immediate Response)
DELETE Request β Controller receives request with ID
Service Processing β Business orchestration and PreBus validation
PreBus Plugins β Sequential validation of business rules
Command Handler β Data processing and soft delete
Domain Logic β Business rules and state management
Response β HTTP 200 OK with success status
Asynchronous Flow (Event Processing)
Event Publishing β ShippingSoftDeletedEvent published to message bus
Subscriber Processing β NotifyLogisticsOnShippingSoftDeleted executes
Side Effects β Logistics updates, carrier notifications, analytics
Query Filtering for Soft Deleted Items
Standard Query Filtering
// In query handlers, always filter out soft deleted items
protected override IQueryable<T> Build<T>()
{
_repoFactory.Init(_params);
IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
// Always exclude soft deleted items
query = query.Where(s => !s.IsSoftDeleted);
// Add other filtering logic here
return query;
}
Include Soft Deleted Items (Admin Queries)
// For admin queries that need to see soft deleted items
protected override IQueryable<T> Build<T>()
{
_repoFactory.Init(_params);
IQueryable<T> query = _repoFactory.GetRepo().FindAll<T>();
// Only include soft deleted items if specifically requested
if (_params.IncludeSoftDeleted == true)
{
// Don't filter out soft deleted items
}
else
{
query = query.Where(s => !s.IsSoftDeleted);
}
return query;
}
Key Benefits
Data Safety: No accidental data loss
Compliance: Meets regulatory requirements
Audit Trail: Complete history of operations
Recovery: Can restore deleted data
Business Rules: Complex deletion logic support
Event-Driven: Notifies other systems of deletions
Testable: Each component can be tested independently
Maintainable: Clear separation of concerns
This SoftDeleteShipping example demonstrates how FlexBase enables clean, maintainable, and scalable soft delete operations with proper validation, data preservation, and event-driven architecture! π
Last updated