Update Example

Overview

This document demonstrates the complete Update flow using the UpdateOrder 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 β†’ 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)
   β”œβ”€β”€ UpdateOrderSequence (Plugin Registration)
   β”œβ”€β”€ UpdateOrderDataPacket (Validation Context)
   └── IsValidOrder 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_UpdateOrder.cs

[HttpPut]
[Route("UpdateOrder")]
[ProducesResponseType(typeof(BadRequestResult), 400)]
[ProducesResponseType(typeof(string), 200)]
public async Task<IActionResult> UpdateOrder([FromBody]UpdateOrderDto dto)
{
    return await RunService(200, dto, _processOrdersService.UpdateOrder);
}

What Happens:

  • HTTP Method: PUT /api/Orders/UpdateOrder

  • Input: UpdateOrderDto from request body

  • Action: Calls the service layer to process the order update

  • Response: HTTP 200 OK with success status

2. Service Layer - Business Orchestration

File: ProcessOrdersService_UpdateOrder.cs

public async Task<CommandResult> UpdateOrder(UpdateOrderDto dto)
{
    var packet = await ProcessBusinessRuleSequence<UpdateOrderDataPacket, UpdateOrderSequence, UpdateOrderDto, FlexAppContextBridge>(dto);

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

        await ProcessCommand(cmd);

        CommandResult cmdResult = new CommandResult(Status.Success);

        UpdateOrderResultModel outputResult = new UpdateOrderResultModel();
        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 UpdateOrderCommand with DTO

  • Command Processing: Calls the command handler

  • Result: Returns success status

2.1. PreBus Business Rule Sequence - Validation Pipeline

File: UpdateOrderSequence.cs

public class UpdateOrderSequence : FlexiBusinessRuleSequenceBase<UpdateOrderDataPacket>
{
    public UpdateOrderSequence()
    {
        this.Add<IsValidOrder>(); 
    }
}

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

public partial class UpdateOrderDataPacket : FlexiFlowDataPacketWithDtoBridge<UpdateOrderDto, FlexAppContextBridge>
{
    protected readonly ILogger<UpdateOrderDataPacket> _logger;

    public UpdateOrderDataPacket(ILogger<UpdateOrderDataPacket> 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: IsValidOrder.cs

public partial class IsValidOrder : FlexiBusinessRuleBase, IFlexiBusinessRule<UpdateOrderDataPacket>
{
    public override string Id { get; set; } = "3a1cd500a49d178e88f75ed6bccaed88";
    public override string FriendlyName { get; set; } = "IsValidOrder";

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

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

    public virtual async Task Validate(UpdateOrderDataPacket 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 - Data Processing

File: UpdateOrderHandler.cs

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

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

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

        await this.Fire(EventCondition, serviceBusContext);
    }
}

What Happens:

  • Context Setup: Initializes application context and repository

  • Entity Lookup: Finds existing order by ID

  • Null Check: Ensures order exists before updating

  • Domain Logic: Calls domain model to process business rules

  • Database Save: 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/UpdateOrder.cs

public virtual Order UpdateOrder(UpdateOrderCommand cmd)
{
    Guard.AgainstNull("Order 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: UpdateOrderNsbHandler.cs

public class UpdateOrderNsbHandler : NsbCommandHandler<UpdateOrderCommand>
{
    readonly ILogger<UpdateOrderNsbHandler> _logger;
    readonly IFlexHost _flexHost;
    readonly IUpdateOrderHandler _handler;

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

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

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

What Happens:

  • Message Reception: Receives UpdateOrderCommand 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: OrderUpdatedEvent

public class OrderUpdatedEvent : 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: UpdateManagerOnUpdateOrder.cs

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

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

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

        //TODO: Write your business logic here:
        // - Notify managers of order changes
        // - Update inventory levels
        // - Send update notifications
        // - Update analytics

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

What Happens:

  • Event Reception: Receives OrderUpdatedEvent from message bus

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

  • Additional Events: Can fire more events if needed

Data Transfer Objects (DTOs)

Input DTO: UpdateOrderDto

public partial class UpdateOrderDto : DtoBridge 
{
    public string Id { get; set; }
    
}

Command: UpdateOrderCommand

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

Key Differences from Insert Flow

Update-Specific Characteristics

  1. Entity Lookup: Must find existing entity before updating

  2. Null Check: Validates entity exists before processing

  3. State Management: Uses SetModified() instead of SetAdded()

  4. Audit Fields: Updates LastModifiedBy instead of CreatedBy

  5. HTTP Method: Uses PUT instead of POST

  6. Response Code: Returns 200 OK instead of 201 Created

PreBus Validation Focus

  • IsValidOrder: Validates order exists and is in updatable state

  • Business Rules: Ensures order can be modified (not shipped, not cancelled)

  • Data Integrity: Validates update doesn't violate business constraints

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 state management

  6. Response β†’ HTTP 200 OK with success status

Asynchronous Flow (Event Processing)

  1. Event Publishing β†’ OrderUpdatedEvent published to message bus

  2. Subscriber Processing β†’ UpdateManagerOnUpdateOrder executes

  3. Side Effects β†’ Manager notifications, inventory 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 UpdateOrder example demonstrates how FlexBase enables clean, maintainable, and scalable update operations with proper validation, audit trails, and event-driven architecture! πŸš€

Last updated