Update Example
Overview
This document demonstrates the complete Update flow using the UpdateShipping feature from the EBusiness application. The flow starts with a PUT request to the controller and ends with event publishing and subscriber processing.
Complete Flow Architecture
PUT Request β Controller β Service β PreBus Plugins β Command Handler β RESTClient β External System
β
Response β Controller β Service β Command Handler β RESTClient β External System
Detailed Flow Breakdown
1. PUT Request
β
2. Controller (API Entry Point)
β
3. Service Layer (Business Orchestration)
β
4. PreBus Processing (Validation Pipeline)
βββ UpdateShippingSequence (Plugin Registration)
βββ UpdateShippingDataPacket (Validation Context)
βββ IsValidShipping 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_UpdateShipping.cs
[HttpPut]
[Route("UpdateShipping")]
[ProducesResponseType(typeof(BadRequestResult), 400)]
[ProducesResponseType(typeof(string), 200)]
public async Task<IActionResult> UpdateShipping([FromBody]UpdateShippingDto dto)
{
return await RunService(200, dto, _processShippingService.UpdateShipping);
}
What Happens:
HTTP Method:
PUT /api/Shipping/UpdateShipping
Input:
UpdateShippingDto
from request bodyAction: Calls the service layer to process the shipping update
Response: HTTP 200 OK with success status
2. Service Layer - Business Orchestration
File: ProcessShippingService_UpdateShipping.cs
public async Task<CommandResult> UpdateShipping(UpdateShippingDto dto)
{
var packet = await ProcessBusinessRuleSequence<UpdateShippingDataPacket, UpdateShippingSequence, UpdateShippingDto, FlexAppContextBridge>(dto);
if (packet.HasError)
{
return new CommandResult(Status.Failed, packet.Errors());
}
else
{
dto.SetGeneratedId(_pkGenerator.GenerateKey());
UpdateShippingCommand cmd = new UpdateShippingCommand
{
Dto = dto,
};
await ProcessCommand(cmd);
CommandResult cmdResult = new CommandResult(Status.Success);
UpdateShippingResultModel outputResult = new UpdateShippingResultModel();
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
UpdateShippingCommand
with DTOCommand Processing: Calls the command handler
Result: Returns success status
2.1. PreBus Business Rule Sequence - Validation Pipeline
File: UpdateShippingSequence.cs
public class UpdateShippingSequence : FlexiBusinessRuleSequenceBase<UpdateShippingDataPacket>
{
public UpdateShippingSequence()
{
this.Add<IsValidShipping>();
}
}
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: UpdateShippingDataPacket.cs
public partial class UpdateShippingDataPacket : FlexiFlowDataPacketWithDtoBridge<UpdateShippingDto, FlexAppContextBridge>
{
protected readonly ILogger<UpdateShippingDataPacket> _logger;
public UpdateShippingDataPacket(ILogger<UpdateShippingDataPacket> 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: IsValidShipping.cs
public partial class IsValidShipping : FlexiBusinessRuleBase, IFlexiBusinessRule<UpdateShippingDataPacket>
{
public override string Id { get; set; } = "3a1cd500a49d178e88f75ed6bccaed88";
public override string FriendlyName { get; set; } = "IsValidShipping";
protected readonly ILogger<IsValidShipping> _logger;
protected readonly RepoFactory _repoFactory;
public IsValidShipping(ILogger<IsValidShipping> logger, RepoFactory repoFactory)
{
_logger = logger;
_repoFactory = repoFactory;
}
public virtual async Task Validate(UpdateShippingDataPacket 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: UpdateShippingHandler.cs
public partial class UpdateShippingHandler : IUpdateShippingHandler
{
protected string EventCondition = "";
protected readonly ILogger<UpdateShippingHandler> _logger;
protected readonly IFlexHost _flexHost;
protected ShippingRESTClient _restClient;
protected FlexAppContextBridge? _flexAppContext;
public UpdateShippingHandler(ILogger<UpdateShippingHandler> logger, IFlexHost flexHost, ShippingRESTClient restClient)
{
_logger = logger;
_flexHost = flexHost;
_restClient = restClient;
}
public virtual async Task Execute(UpdateShippingCommand cmd, IFlexServiceBusContext serviceBusContext)
{
// Convert internal DTO to Request DTO
UpdateShippingRequestDto requestParams = new UpdateShippingRequestDto
{
Id = cmd.Dto.Id,
ShippingAddress = cmd.Dto.ShippingAddress,
TrackingNumber = cmd.Dto.TrackingNumber,
Status = cmd.Dto.Status.ToString(),
TotalWeight = cmd.Dto.ShippingItems?.Sum(x => x.Weight) ?? 0,
ShippingItems = cmd.Dto.ShippingItems?.Select(x => new ShippingItemRequestDto
{
ProductId = x.ProductId,
Quantity = x.Qty,
Weight = x.Weight,
Volume = x.Volume
}).ToList()
};
// Call external REST service
var response = await _restClient.UpdateShipping(requestParams);
// Process response
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
var responseDto = JsonConvert.DeserializeObject<UpdateShippingResponseDto>(responseContent);
_logger.LogDebug("Shipping updated successfully with external ID: {ExternalId}", responseDto?.Id);
// Set success condition for event firing
EventCondition = "CONDITION_ONSUCCESS";
}
else
{
_logger.LogError("Failed to update 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/UpdateShipping.cs
public virtual Shipping UpdateShipping(UpdateShippingCommand cmd)
{
Guard.AgainstNull("Shipping model cannot be empty", cmd);
this.Convert(cmd.Dto);
this.LastModifiedBy = cmd.Dto.GetAppContext()?.UserId;
//Map any other field not handled by Automapper config
this.SetModified();
//Set your appropriate SetModified for the inner object here
return this;
}
What Happens:
Validation: Guards against null commands
Data Mapping: Converts DTO to domain model
Audit Fields: Sets last modified by user
State Management: Marks entity as modified
Child Objects: Processes child object updates
5. NServiceBus Handler - Message Processing
File: UpdateShippingNsbHandler.cs
public class UpdateShippingNsbHandler : NsbCommandHandler<UpdateShippingCommand>
{
readonly ILogger<UpdateShippingNsbHandler> _logger;
readonly IFlexHost _flexHost;
readonly IUpdateShippingHandler _handler;
public UpdateShippingNsbHandler(ILogger<UpdateShippingNsbHandler> logger, IFlexHost flexHost, IUpdateShippingHandler handler)
{
_logger = logger;
_flexHost = flexHost;
_handler = handler;
}
public override async Task Handle(UpdateShippingCommand message, IMessageHandlerContext context)
{
_logger.LogTrace($"Executing {nameof(UpdateShippingNsbHandler)}");
await _handler.Execute(message, new NSbHandlerContextBridge(context));
}
}
What Happens:
Message Reception: Receives
UpdateShippingCommand
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: ShippingUpdatedEvent
public class ShippingUpdatedEvent : 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: UpdateLogisticsOnUpdateShipping.cs
public partial class UpdateLogisticsOnUpdateShipping : IUpdateLogisticsOnUpdateShipping
{
protected readonly ILogger<UpdateLogisticsOnUpdateShipping> _logger;
protected string EventCondition = "";
public UpdateLogisticsOnUpdateShipping(ILogger<UpdateLogisticsOnUpdateShipping> logger)
{
_logger = logger;
}
public virtual async Task Execute(ShippingUpdatedEvent @event, IFlexServiceBusContext serviceBusContext)
{
_flexAppContext = @event.AppContext;
//TODO: Write your business logic here:
// - Notify logistics of shipping changes
// - Update tracking information
// - Send update notifications
// - Update analytics
await this.Fire<UpdateLogisticsOnUpdateShipping>(EventCondition, serviceBusContext);
}
}
What Happens:
Event Reception: Receives
ShippingUpdatedEvent
from message busSide Effects: Executes business logic (notifications, tracking updates, etc.)
Additional Events: Can fire more events if needed
Data Transfer Objects (DTOs)
Input DTO: UpdateShippingDto
UpdateShippingDto
public partial class UpdateShippingDto : DtoBridge
{
public string Id { get; set; }
[StringLength(200)]
public string ShippingAddress { get; set; }
public string TrackingNumber { get; set; }
public ShippingStatus Status { get; set; }
}
Command: UpdateShippingCommand
UpdateShippingCommand
public class UpdateShippingCommand : FlexCommandBridge<UpdateShippingDto, FlexAppContextBridge>
{
// Command data is automatically populated by FlexBase
}
Key Differences from Insert Flow
Update-Specific Characteristics
Entity Lookup: Must find existing entity before updating
Null Check: Validates entity exists before processing
State Management: Uses
SetModified()
instead ofSetAdded()
Audit Fields: Updates
LastModifiedBy
instead ofCreatedBy
HTTP Method: Uses
PUT
instead ofPOST
Response Code: Returns
200 OK
instead of201 Created
PreBus Validation Focus
IsValidShipping: Validates shipping exists and is in updatable state
Business Rules: Ensures shipping can be modified (not delivered, not cancelled)
Data Integrity: Validates update doesn't violate business constraints
Flow Summary
Synchronous Flow (Immediate Response)
PUT Request β Controller receives request
Service Processing β Business orchestration and PreBus validation
PreBus Plugins β Sequential validation of business rules
Command Handler β Entity lookup and data processing
Domain Logic β Business rules and state management
Response β HTTP 200 OK with success status
Asynchronous Flow (Event Processing)
Event Publishing β ShippingUpdatedEvent published to message bus
Subscriber Processing β UpdateLogisticsOnUpdateShipping executes
Side Effects β Logistics notifications, tracking updates, analytics
Key Benefits
Data Integrity: Ensures entity exists before updating
Audit Trail: Tracks who made changes and when
Business Rules: Validates update is allowed
Event-Driven: Notifies other systems of changes
Testable: Each component can be tested independently
Maintainable: Clear separation of concerns
This UpdateShipping example demonstrates how FlexBase enables clean, maintainable, and scalable update operations with proper validation, audit trails, and event-driven architecture! π
Last updated