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 bodyAction: 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 DTOCommand 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 busLogging: 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 busSide Effects: Executes business logic (emails, notifications, etc.)
Additional Events: Can fire more events if needed
Data Transfer Objects (DTOs)
Input DTO: AddOrderDto
AddOrderDto
public partial class AddOrderDto : DtoBridge
{
[StringLength(100)]
public string CustomerId { get; set; }
public ICollection<AddOrderDto_OrderItem> OrderItems { get; set; }
}
Command: AddOrderCommand
AddOrderCommand
public class AddOrderCommand : FlexCommandBridge<AddOrderDto, FlexAppContextBridge>
{
// Command data is automatically populated by FlexBase
}
Flow Summary
Synchronous Flow (Immediate Response)
POST Request β Controller receives request
Service Processing β Business orchestration and PreBus validation
PreBus Plugins β Sequential validation of business rules
Command Handler β Data processing and database save
Domain Logic β Business rules and calculations
Response β HTTP 201 with order ID
Asynchronous Flow (Event Processing)
Event Publishing β OrderAddedEvent published to message bus
Subscriber Processing β NotifyAccountsOnOrderAdded executes
Side Effects β Emails, notifications, inventory updates
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