> For the complete documentation index, see [llms.txt](https://docs.flexbase.in/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flexbase.in/solution-structure/getting-started/features/crud/soft-delete-example.md).

# Soft Delete Example

## Overview

This document demonstrates the complete **Soft Delete flow** using a **SoftDeleteProduct** feature. The flow is similar to an Update operation (PUT) but uses `SetSoftDelete()` instead of `SetModified()` or `SetDeleted()`. This pattern allows for reversible deletions while maintaining data integrity.

## Complete Flow Architecture

```
PUT Request → Controller → Service → PreBus Plugins → Command Handler → Domain Model → Database → Event Publishing → Subscribers
```

### **Detailed Flow Breakdown**

```
1. PUT Request
   ↓
2. Controller (API Entry Point)
   ↓
3. Service Layer (Business Orchestration)
   ↓
4. PreBus Processing (Validation Pipeline)
   ├── SoftDeleteProductSequence (Plugin Registration)
   ├── SoftDeleteProductDataPacket (Validation Context)
   └── IsValidForSoftDelete Plugin (Business Rules)
   ↓
5. Command Handler (Data Processing)
   ↓
6. Domain Model (Business Logic)
   ↓
7. Database (Data Persistence)
   ↓
8. Event Publishing (Asynchronous Processing)
   ↓
9. Subscribers (Side Effects)
```

## Step-by-Step Implementation

### 1. **API Controller** - The Entry Point

**File**: `ProductsController_SoftDeleteProduct.cs`

```csharp
[HttpPut]
[Route("SoftDeleteProduct")]
[ProducesResponseType(typeof(BadRequestResult), 400)]
[ProducesResponseType(typeof(string), 200)]
public async Task<IActionResult> SoftDeleteProduct([FromBody]SoftDeleteProductDto dto)
{
    return await RunService(200, dto, _processProductsService.SoftDeleteProduct);
}
```

**What Happens:**

* **HTTP Method**: `PUT /api/Products/SoftDeleteProduct`
* **Input**: `SoftDeleteProductDto` from request body
* **Action**: Calls the service layer to process the soft delete
* **Response**: HTTP 200 OK with success status

### 2. **Service Layer** - Business Orchestration

**File**: `ProcessProductsService_SoftDeleteProduct.cs`

```csharp
public async Task<CommandResult> SoftDeleteProduct(SoftDeleteProductDto dto)
{
    var packet = await ProcessBusinessRuleSequence<SoftDeleteProductDataPacket, SoftDeleteProductSequence, SoftDeleteProductDto, FlexAppContextBridge>(dto);

    if (packet.HasError)
    {
        return new CommandResult(Status.Failed, packet.Errors());
    }
    else
    {
        dto.SetGeneratedId(_pkGenerator.GenerateKey());
        SoftDeleteProductCommand cmd = new SoftDeleteProductCommand
        {
            Dto = dto,
        };

        await ProcessCommand(cmd);

        CommandResult cmdResult = new CommandResult(Status.Success);

        SoftDeleteProductResultModel outputResult = new SoftDeleteProductResultModel();
        outputResult.Id = dto.Id;
        outputResult.IsSoftDeleted = true;
        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 `SoftDeleteProductCommand` with DTO
* **Command Processing**: Calls the command handler
* **Result**: Returns success with soft delete status

### 2.1. **PreBus Business Rule Sequence** - Validation Pipeline

**File**: `SoftDeleteProductSequence.cs`

```csharp
public class SoftDeleteProductSequence : FlexiBusinessRuleSequenceBase<SoftDeleteProductDataPacket>
{
    public SoftDeleteProductSequence()
    {
        this.Add<IsValidForSoftDelete>(); 
        this.Add<CheckProductDependencies>();
    }
}
```

**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**: `SoftDeleteProductDataPacket.cs`

```csharp
public partial class SoftDeleteProductDataPacket : FlexiFlowDataPacketWithDtoBridge<SoftDeleteProductDto, FlexAppContextBridge>
{
    protected readonly ILogger<SoftDeleteProductDataPacket> _logger;

    public SoftDeleteProductDataPacket(ILogger<SoftDeleteProductDataPacket> logger)
    {
        _logger = logger;
    }

    #region "Properties
    //Models and other properties goes here
    public Product Product { get; set; }
    public bool HasActiveOrders { get; set; }
    public bool HasActiveInventory { get; set; }
    #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**: `IsValidForSoftDelete.cs`

```csharp
public partial class IsValidForSoftDelete : FlexiBusinessRuleBase, IFlexiBusinessRule<SoftDeleteProductDataPacket>
{
    public override string Id { get; set; } = "3a1cd5b1fa98b4c63378de9607706083";
    public override string FriendlyName { get; set; } = "IsValidForSoftDelete";

    protected readonly ILogger<IsValidForSoftDelete> _logger;
    protected readonly RepoFactory _repoFactory;

    public IsValidForSoftDelete(ILogger<IsValidForSoftDelete> logger, RepoFactory repoFactory)
    {
        _logger = logger;
        _repoFactory = repoFactory;
    }

    public virtual async Task Validate(SoftDeleteProductDataPacket packet)
    {
        _repoFactory.Init(packet.Dto);
        
        // Check if product exists
        var product = _repoFactory.GetRepo().FindAll<Product>()
            .Where(p => p.Id == packet.Dto.Id && !p.IsSoftDeleted)
            .FirstOrDefault();
            
        if (product == null)
        {
            packet.AddError("ProductNotFound", "Product not found or already soft deleted");
            return;
        }
        
        packet.Product = product;
        
        // Check if product has active orders
        var hasActiveOrders = _repoFactory.GetRepo().FindAll<OrderItem>()
            .Any(oi => oi.ProductId == packet.Dto.Id && !oi.Order.IsSoftDeleted);
            
        if (hasActiveOrders)
        {
            packet.AddError("HasActiveOrders", "Cannot soft delete product with active orders");
        }
        
        packet.HasActiveOrders = hasActiveOrders;

        await Task.CompletedTask;
    }
}
```

**What Happens:**

* **Business Rule Validation**: Implements specific validation logic
* **Database Access**: Validates product exists and can be soft deleted
* **Dependency Checking**: Ensures no active orders reference the product
* **Error Reporting**: Adds errors to the data packet if validation fails
* **Data Sharing**: Populates packet with validation results

### 3. **Command Handler** - Data Processing

**File**: `SoftDeleteProductHandler.cs`

```csharp
public virtual async Task Execute(SoftDeleteProductCommand cmd, IFlexServiceBusContext serviceBusContext)
{
    _flexAppContext = cmd.Dto.GetAppContext();
    _repoFactory.Init(cmd.Dto);

    _model = _repoFactory.GetRepo().FindAll<Product>().Where(m=>m.Id == cmd.Dto.Id).FirstOrDefault();
    
    if (_model != null)
    {
        _model.SoftDeleteProduct(cmd);
        _repoFactory.GetRepo().InsertOrUpdate(_model);

        int records = await _repoFactory.GetRepo().SaveAsync();
        if (records > 0)
        {
            _logger.LogDebug("{} with {} soft deleted in Database: ", typeof(Product).Name, _model.Id);
        }
        else
        {
            _logger.LogWarning("No records soft deleted for {} with {}", typeof(Product).Name, _model.Id);
        }

        EventCondition = CONDITION_ONSUCCESS;
    }
    else
    {
        _logger.LogWarning("Product with ID {} not found for soft delete", cmd.Dto.Id);
        EventCondition = CONDITION_ONFAILED;
    }
    
    await this.Fire(EventCondition, serviceBusContext);
}
```

**What Happens:**

* **Context Setup**: Initializes application context and repository
* **Entity Lookup**: Finds existing product by ID
* **Null Check**: Ensures product exists before soft deleting
* **Domain Logic**: Calls domain model to process business rules
* **Database Save**: Updates the product in database (soft delete)
* **Logging**: Logs success/failure of database operation
* **Event Publishing**: Fires events for subscribers

### 4. **Domain Model** - Business Logic

**File**: `Product/SoftDeleteProduct.cs`

```csharp
public virtual Product SoftDeleteProduct(SoftDeleteProductCommand cmd)
{
    Guard.AgainstNull("Product model cannot be empty", cmd);

    this.Convert(cmd.Dto);
    this.LastModifiedBy = cmd.Dto.GetAppContext()?.UserId;
    this.SoftDeletedBy = cmd.Dto.GetAppContext()?.UserId;
    this.SoftDeletedAt = DateTime.UtcNow;

    //Map any other field not handled by Automapper config

    this.SetSoftDelete();

    //Set your appropriate SetSoftDelete for the inner object here
    this.OrderItems?.SetSoftDelete();

    return this;
}
```

**What Happens:**

* **Validation**: Guards against null commands
* **Data Mapping**: Converts DTO to domain model
* **Audit Fields**: Sets last modified by and soft deleted by user
* **Timestamp**: Records when the soft delete occurred
* **State Management**: Uses `SetSoftDelete()` instead of `SetModified()` or `SetDeleted()`
* **Child Objects**: Processes child object soft deletions

### 5. **NServiceBus Handler** - Message Processing

**File**: `SoftDeleteProductNsbHandler.cs`

```csharp
public class SoftDeleteProductNsbHandler : NsbCommandHandler<SoftDeleteProductCommand>
{
    readonly ILogger<SoftDeleteProductNsbHandler> _logger;
    readonly IFlexHost _flexHost;
    readonly ISoftDeleteProductHandler _handler;

    public SoftDeleteProductNsbHandler(ILogger<SoftDeleteProductNsbHandler> logger, IFlexHost flexHost, ISoftDeleteProductHandler handler)
    {
        _logger = logger;
        _flexHost = flexHost;
        _handler = handler;
    }

    public override async Task Handle(SoftDeleteProductCommand message, IMessageHandlerContext context)
    {
        _logger.LogTrace($"Executing {nameof(SoftDeleteProductNsbHandler)}");

        await _handler.Execute(message, new NsbHandlerContextBridge(context));
    }
}
```

**What Happens:**

* **Message Reception**: Receives `SoftDeleteProductCommand` from message bus
* **Logging**: Logs handler execution
* **Delegation**: Calls the actual command handler
* **Context Bridge**: Converts NServiceBus context to FlexBase context

### 6. **Event Publishing** - Asynchronous Processing

**Event**: `ProductSoftDeletedEvent`

```csharp
public class ProductSoftDeletedEvent : FlexEventBridge<FlexAppContextBridge>
{
    // Event data is automatically populated by FlexBase
}
```

**What Happens:**

* **Event Creation**: FlexBase creates event with product data
* **Message Bus**: Event is published to message bus
* **Subscriber Notification**: All subscribers are notified

### 7. **Event Subscribers** - Side Effects

**File**: `NotifyInventoryOnProductSoftDeleted.cs`

```csharp
public partial class NotifyInventoryOnProductSoftDeleted : INotifyInventoryOnProductSoftDeleted
{
    protected readonly ILogger<NotifyInventoryOnProductSoftDeleted> _logger;
    protected string EventCondition = "";

    public NotifyInventoryOnProductSoftDeleted(ILogger<NotifyInventoryOnProductSoftDeleted> logger)
    {
        _logger = logger;
    }

    public virtual async Task Execute(ProductSoftDeletedEvent @event, IFlexServiceBusContext serviceBusContext)
    {
        _flexAppContext = @event.AppContext;

        //TODO: Write your business logic here:
        // - Update inventory status
        // - Notify suppliers about product unavailability
        // - Update analytics and reporting
        // - Archive related data
        // - Send notifications to stakeholders

        await this.Fire<NotifyInventoryOnProductSoftDeleted>(EventCondition, serviceBusContext);
    }
}
```

**What Happens:**

* **Event Reception**: Receives `ProductSoftDeletedEvent` from message bus
* **Side Effects**: Executes business logic (inventory updates, notifications, etc.)
* **Additional Events**: Can fire more events if needed

## Data Transfer Objects (DTOs)

### **Input DTO**: `SoftDeleteProductDto`

```csharp
public partial class SoftDeleteProductDto : DtoBridge 
{
    public string Id { get; set; }
    public string Reason { get; set; }
    public string Comments { get; set; }
}
```

### **Command**: `SoftDeleteProductCommand`

```csharp
public class SoftDeleteProductCommand : FlexCommandBridge<SoftDeleteProductDto, FlexAppContextBridge>
{
    // Command data is automatically populated by FlexBase
}
```

## Key Differences from Regular Delete/Update

### **Soft Delete-Specific Characteristics**

1. **Reversible Operation**: Can be undone unlike hard delete
2. **State Management**: Uses `SetSoftDelete()` instead of `SetDeleted()` or `SetModified()`
3. **Audit Trail**: Tracks who soft deleted and when
4. **Business Logic**: Includes reason and comments for soft delete
5. **Data Preservation**: Data remains in database but marked as soft deleted
6. **Query Filtering**: Soft-deleted entities are filtered out of normal queries

### **PreBus Validation Focus**

* **IsValidForSoftDelete**: Validates product exists and can be soft deleted
* **CheckProductDependencies**: Ensures no active references to the product
* **Business Rules**: Validates soft delete is allowed based on business constraints
* **Data Integrity**: Ensures soft delete doesn't violate business rules

### **Soft Delete vs Hard Delete vs Update**

| Aspect              | Soft Delete       | Hard Delete       | Update            |
| ------------------- | ----------------- | ----------------- | ----------------- |
| **Method**          | `SetSoftDelete()` | `SetDeleted()`    | `SetModified()`   |
| **Reversible**      | ✅ Yes             | ❌ No              | ✅ Yes             |
| **Data Preserved**  | ✅ Yes             | ❌ No              | ✅ Yes             |
| **Audit Trail**     | ✅ Full            | ✅ Limited         | ✅ Full            |
| **Query Filtering** | ✅ Filtered        | ❌ Removed         | ✅ Normal          |
| **Use Case**        | Temporary removal | Permanent removal | Data modification |

## Flow Summary

### **Synchronous Flow (Immediate Response)**

1. **PUT Request** → Controller receives request
2. **Service Processing** → Business orchestration and PreBus validation
3. **PreBus Plugins** → Sequential validation of business rules
4. **Command Handler** → Entity lookup and data processing
5. **Domain Logic** → Business rules and soft delete state management
6. **Response** → HTTP 200 OK with soft delete status

### **Asynchronous Flow (Event Processing)**

1. **Event Publishing** → ProductSoftDeletedEvent published to message bus
2. **Subscriber Processing** → NotifyInventoryOnProductSoftDeleted executes
3. **Side Effects** → Inventory updates, supplier notifications, analytics

## Key Benefits

* **Data Safety**: Preserves data for potential recovery
* **Business Continuity**: Maintains referential integrity
* **Audit Compliance**: Complete audit trail of soft deletions
* **Reversible Operations**: Can be undone if needed
* **Business Rules**: Validates soft delete is appropriate
* **Event-Driven**: Notifies other systems of soft deletions
* **Testable**: Each component can be tested independently
* **Maintainable**: Clear separation of concerns

***

**This SoftDeleteProduct example demonstrates how FlexBase enables clean, maintainable, and scalable soft delete operations with proper validation, reversible state management, and event-driven architecture!** 🚀
