Insert Example

Overview

This document demonstrates the complete Insert flow using the AddOrder feature from the EBusiness application. The flow starts with a POST request to the controller and ends with event publishing and subscriber processing.

Complete Flow Architecture

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

Detailed Flow Breakdown

1. POST Request

2. Controller (API Entry Point)

3. Service Layer (Business Orchestration)

4. PreBus Processing (Validation Pipeline)
   ├── AddOrderSequence (Plugin Registration)
   ├── AddOrderDataPacket (Validation Context)
   └── ValidateCustomer 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: OrdersController_AddOrder.cs

[HttpPost]
[Route("AddOrder")]
[ProducesResponseType(typeof(BadRequestResult), 400)]
[ProducesResponseType(typeof(string), 201)]
public async Task<IActionResult> AddOrder([FromBody]AddOrderDto dto)
{
    return await RunService(201, dto, _processOrdersService.AddOrder);
}

What Happens:

  • HTTP Method: POST /api/Orders/AddOrder

  • Input: AddOrderDto from request body

  • Action: Calls the service layer to process the order

  • Response: HTTP 201 Created with order ID

2. Service Layer - Business Orchestration

File: ProcessOrdersService_AddOrder.cs

public async Task<CommandResult> AddOrder(AddOrderDto dto)
{
    var packet = await ProcessBusinessRuleSequence<AddOrderDataPacket, AddOrderSequence, AddOrderDto, FlexAppContextBridge>(dto);

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

        await ProcessCommand(cmd);

        CommandResult cmdResult = new CommandResult(Status.Success);

        AddOrderResultModel outputResult = new AddOrderResultModel();
        outputResult.Id = dto.GetGeneratedId();
        cmdResult.result = outputResult;

        return cmdResult;
    }
}

What Happens:

  • PreBus Processing: Executes business rule sequences (plugins)

  • Validation: Processes business rule sequences

  • ID Generation: Generates unique order ID

  • Command Creation: Creates AddOrderCommand with DTO

  • Command Processing: Calls the command handler

  • Result: Returns success with generated order ID

2.1. PreBus Business Rule Sequence - Validation Pipeline

File: AddOrderSequence.cs

public class AddOrderSequence : FlexiBusinessRuleSequenceBase<AddOrderDataPacket>
{
    public AddOrderSequence()
    {
        this.Add<ValidateCustomer>(); 
    }
}

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: AddOrderDataPacket.cs

public partial class AddOrderDataPacket : FlexiFlowDataPacketWithDtoBridge<AddOrderDto, FlexAppContextBridge>
{
    protected readonly ILogger<AddOrderDataPacket> _logger;

    public AddOrderDataPacket(ILogger<AddOrderDataPacket> 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: ValidateCustomer.cs

public partial class ValidateCustomer : FlexiBusinessRuleBase, IFlexiBusinessRule<AddOrderDataPacket>
{
    public override string Id { get; set; } = "3a1cd0401f931dd4a01587e229b720dc";
    public override string FriendlyName { get; set; } = "ValidateCustomer";

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

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

    public virtual async Task Validate(AddOrderDataPacket 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

2.4. PreBus Plugin Benefits

  • Modular Validation: Each plugin handles one validation concern

  • Reusable Rules: Plugins can be reused across different features

  • Testable: Each plugin can be unit tested independently

  • Configurable: Plugins can be enabled/disabled per feature

  • Extensible: Easy to add new validation rules

  • Ordered Execution: Plugins execute in defined sequence

3. Command Handler - Data Processing

File: AddOrderHandler.cs

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

    _model = _flexHost.GetDomainModel<Order>().AddOrder(cmd);
    _repoFactory.GetRepo().InsertOrUpdate(_model);
    int records = await _repoFactory.GetRepo().SaveAsync();
    
    if (records > 0)
    {
        _logger.LogDebug("{Entity} with {EntityId} inserted into Database: ", typeof(Order).Name, _model.Id);
    }
    else
    {
        _logger.LogWarning("No records inserted for {Entity} with {EntityId}", typeof(Order).Name, _model.Id);
    }
    
    await this.Fire(EventCondition, serviceBusContext);
}

What Happens:

  • Context Setup: Initializes application context and repository

  • Domain Logic: Calls domain model to process business rules

  • Database Save: Inserts/updates the order in database

  • Logging: Logs success/failure of database operation

  • Event Publishing: Fires events for subscribers

4. Domain Model - Business Logic

File: Order/AddOrder.cs

public virtual Order AddOrder(AddOrderCommand cmd)
{
    Guard.AgainstNull("Order command cannot be empty", cmd);

    this.Convert(cmd.Dto);
    this.CreatedBy = cmd.Dto.GetAppContext()?.UserId;
    this.LastModifiedBy = cmd.Dto.GetAppContext()?.UserId;

    this.SetAdded(cmd.Dto.GetGeneratedId());

    //Set your appropriate SetAdded for the inner object here
    this.OrderState = new OrderIsCreated().SetTFlexId(this.Id).SetStateChangedBy("");
    this.TotalAmount = this.OrderItems.Select(s => s.SellingPrice).Sum();
    this.TotalQty = this.OrderItems.Select(s => s.Qty).Sum();

    this.OrderItems.SetAddedOrModified();

    return this;
}

What Happens:

  • Validation: Guards against null commands

  • Data Mapping: Converts DTO to domain model

  • Audit Fields: Sets created/modified by user

  • ID Assignment: Sets the generated ID

  • Business Rules: Calculates totals, sets order state

  • Child Objects: Processes order items

5. NServiceBus Handler - Message Processing

File: AddOrderNsbHandler.cs

public class AddOrderNsbHandler : NsbCommandHandler<AddOrderCommand>
{
    readonly ILogger<AddOrderNsbHandler> _logger;
    readonly IFlexHost _flexHost;
    readonly IAddOrderHandler _handler;

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

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

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

What Happens:

  • Message Reception: Receives AddOrderCommand 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: OrderAddedEvent

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

What Happens:

  • Event Creation: FlexBase creates event with order data

  • Message Bus: Event is published to message bus

  • Subscriber Notification: All subscribers are notified

7. Event Subscribers - Side Effects

File: NotifyAccountsOnOrderAdded.cs

public partial class NotifyAccountsOnOrderAdded : INotifyAccountsOnOrderAdded
{
    protected readonly ILogger<NotifyAccountsOnOrderAdded> _logger;
    protected string EventCondition = "";

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

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

        //TODO: Write your business logic here:
        // - Send confirmation email
        // - Update inventory
        // - Notify accounting system
        // - Update analytics

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

What Happens:

  • Event Reception: Receives OrderAddedEvent from message bus

  • Side Effects: Executes business logic (emails, notifications, etc.)

  • Additional Events: Can fire more events if needed

Data Transfer Objects (DTOs)

Input DTO: AddOrderDto

public partial class AddOrderDto : DtoBridge 
{
    [StringLength(100)]
    public string CustomerId { get; set; }

    public ICollection<AddOrderDto_OrderItem> OrderItems { get; set; }
}

Command: AddOrderCommand

public class AddOrderCommand : FlexCommandBridge<AddOrderDto, FlexAppContextBridge>
{
    // Command data is automatically populated by FlexBase
}

Flow Summary

Synchronous Flow (Immediate Response)

  1. POST Request → Controller receives request

  2. Service Processing → Business orchestration and PreBus validation

  3. PreBus Plugins → Sequential validation of business rules

  4. Command Handler → Data processing and database save

  5. Domain Logic → Business rules and calculations

  6. Response → HTTP 201 with order ID

Asynchronous Flow (Event Processing)

  1. Event Publishing → OrderAddedEvent published to message bus

  2. Subscriber Processing → NotifyAccountsOnOrderAdded executes

  3. Side Effects → Emails, notifications, inventory updates

  4. Additional Events → Can trigger more business processes

Key Benefits

  • Separation of Concerns: Each layer has a single responsibility

  • Testability: Each component can be tested independently

  • Scalability: Asynchronous processing handles high loads

  • Maintainability: Clear, readable code structure

  • Event-Driven: Loose coupling between components


This AddOrder example demonstrates how FlexBase enables clean, maintainable, and scalable insert operations with proper separation of concerns and event-driven architecture! 🚀

Last updated