# 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!** 🚀


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flexbase.in/solution-structure/getting-started/features/crud/soft-delete-example.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
