22 KiB
22 KiB
Business Rules and Logic Patterns - Centron Enterprise Application
Overview
This document provides comprehensive coverage of business logic patterns and implementations within the Centron .NET 8 enterprise application. Business rules encompass decision logic, workflow patterns, process orchestration, state management, and calculation patterns that enforce business constraints and processes.
Business Logic Architecture
Core Business Logic Patterns
1. Business Logic Layer (BL) Pattern
The foundation of all business rule implementation follows a consistent 2-layer architecture:
// Base BL Class - Core Business Logic
public class AccountBL : BaseBL
{
public Result<Account> SaveAccount(Account account)
{
// Business rule validation
if (account.CustomerNumber <= 0)
return Result<Account>.AsError("Customer number must be positive");
// Business logic execution
var validationResult = ValidateAccountBusinessRules(account);
if (validationResult.Status != ResultStatus.Success)
return Result<Account>.FromResult(validationResult);
// Data persistence
return _accountDAO.SaveAccount(account);
}
private Result ValidateAccountBusinessRules(Account account)
{
// Complex business rule validation logic
if (account.CreditLimit < 0 && !account.IsSpecialCustomer)
return Result.AsError("Negative credit limit only allowed for special customers");
return Result.AsSuccess();
}
}
// WebService BL Class - DTO Conversion + Business Logic
public class AccountWebServiceBL : BaseBL
{
private readonly AccountBL _accountBL;
public AccountWebServiceBL()
{
_accountBL = new AccountBL();
}
public Result<AccountDTO> SaveAccount(AccountDTO accountDTO)
{
// Convert DTO to Entity
var account = ConvertAccountDTOToAccount(accountDTO);
// Execute business logic
var result = _accountBL.SaveAccount(account);
// Convert back to DTO
return result.Status == ResultStatus.Success
? Result<AccountDTO>.AsSuccess(ObjectMapper.Map<AccountDTO>(result.Data))
: Result<AccountDTO>.FromResult(result);
}
}
Decision Logic and Rule Implementations
1. Rule Engine Pattern
Business Rule Validation Chain
public class BusinessRuleValidator<T>
{
private readonly List<IBusinessRule<T>> _rules = new();
public BusinessRuleValidator<T> AddRule(IBusinessRule<T> rule)
{
_rules.Add(rule);
return this;
}
public Result ValidateAll(T entity)
{
foreach (var rule in _rules)
{
var result = rule.Validate(entity);
if (result.Status != ResultStatus.Success)
return result;
}
return Result.AsSuccess();
}
}
// Example Business Rule Implementation
public class CustomerCreditLimitRule : IBusinessRule<Account>
{
public Result Validate(Account account)
{
if (account.CreditLimit > 100000 && account.PaymentTerms > 30)
return Result.AsError("High credit limit requires payment terms <= 30 days");
return Result.AsSuccess();
}
}
// Usage in Business Logic
public Result<Account> ProcessAccountApproval(Account account)
{
var validator = new BusinessRuleValidator<Account>()
.AddRule(new CustomerCreditLimitRule())
.AddRule(new CustomerTypeValidationRule())
.AddRule(new RegionalComplianceRule());
var validationResult = validator.ValidateAll(account);
if (validationResult.Status != ResultStatus.Success)
return Result<Account>.FromResult(validationResult);
return ApproveAccount(account);
}
2. Conditional Business Logic Pattern
Strategy Pattern for Business Decisions
public interface IPricingStrategy
{
Result<decimal> CalculatePrice(PricingContext context);
}
public class StandardPricingStrategy : IPricingStrategy
{
public Result<decimal> CalculatePrice(PricingContext context)
{
var basePrice = context.Article.BasePrice;
var discount = CalculateStandardDiscount(context.Customer);
return Result<decimal>.AsSuccess(basePrice * (1 - discount));
}
}
public class VipPricingStrategy : IPricingStrategy
{
public Result<decimal> CalculatePrice(PricingContext context)
{
var basePrice = context.Article.BasePrice;
var vipDiscount = CalculateVipDiscount(context.Customer, context.Volume);
return Result<decimal>.AsSuccess(basePrice * (1 - vipDiscount));
}
}
// Business Logic Using Strategy
public class PricingBL : BaseBL
{
public Result<decimal> CalculateArticlePrice(int customerId, int articleId, int quantity)
{
var context = GetPricingContext(customerId, articleId, quantity);
var strategy = GetPricingStrategy(context.Customer.CustomerType);
return strategy.CalculatePrice(context);
}
private IPricingStrategy GetPricingStrategy(CustomerType customerType)
{
return customerType switch
{
CustomerType.Vip => new VipPricingStrategy(),
CustomerType.Corporate => new CorporatePricingStrategy(),
_ => new StandardPricingStrategy()
};
}
}
Business Workflow Patterns
1. Process Orchestration Pattern
Receipt Processing Workflow
public class ReceiptProcessingWorkflow
{
private readonly IReceiptLogic _receiptLogic;
private readonly IInventoryLogic _inventoryLogic;
private readonly IFinancialLogic _financialLogic;
public async Task<Result<ProcessedReceipt>> ProcessReceipt(ReceiptDTO receipt)
{
// Step 1: Validate Receipt
var validationResult = await ValidateReceiptBusinessRules(receipt);
if (validationResult.Status != ResultStatus.Success)
return Result<ProcessedReceipt>.FromResult(validationResult);
// Step 2: Reserve Inventory
var reservationResult = await _inventoryLogic.ReserveInventory(receipt.Items);
if (reservationResult.Status != ResultStatus.Success)
{
await CompensateReservation(reservationResult.Data);
return Result<ProcessedReceipt>.FromResult(reservationResult);
}
// Step 3: Process Payment
var paymentResult = await _financialLogic.ProcessPayment(receipt.PaymentInfo);
if (paymentResult.Status != ResultStatus.Success)
{
await CompensateInventory(reservationResult.Data);
return Result<ProcessedReceipt>.FromResult(paymentResult);
}
// Step 4: Finalize Receipt
var finalResult = await _receiptLogic.FinalizeReceipt(receipt, paymentResult.Data);
return Result<ProcessedReceipt>.AsSuccess(new ProcessedReceipt
{
Receipt = finalResult.Data,
ReservationId = reservationResult.Data.ReservationId,
PaymentId = paymentResult.Data.PaymentId
});
}
private async Task<Result> ValidateReceiptBusinessRules(ReceiptDTO receipt)
{
// Business rule: Receipt total must match item totals
var calculatedTotal = receipt.Items.Sum(i => i.Quantity * i.Price);
if (Math.Abs(receipt.Total - calculatedTotal) > 0.01m)
return Result.AsError("Receipt total does not match item totals");
// Business rule: Customer credit limit check
var customerResult = await _receiptLogic.GetCustomer(receipt.CustomerId);
if (customerResult.Status == ResultStatus.Success)
{
var creditCheck = await CheckCreditLimit(customerResult.Data, receipt.Total);
if (creditCheck.Status != ResultStatus.Success)
return creditCheck;
}
return Result.AsSuccess();
}
}
2. State Machine Implementation Pattern
Helpdesk Ticket State Management
public enum HelpdeskState
{
Created,
Assigned,
InProgress,
WaitingForCustomer,
Resolved,
Closed,
Cancelled
}
public class HelpdeskStateMachine
{
private static readonly Dictionary<HelpdeskState, List<HelpdeskState>> _validTransitions = new()
{
{ HelpdeskState.Created, new() { HelpdeskState.Assigned, HelpdeskState.Cancelled } },
{ HelpdeskState.Assigned, new() { HelpdeskState.InProgress, HelpdeskState.Cancelled } },
{ HelpdeskState.InProgress, new() { HelpdeskState.WaitingForCustomer, HelpdeskState.Resolved, HelpdeskState.Cancelled } },
{ HelpdeskState.WaitingForCustomer, new() { HelpdeskState.InProgress, HelpdeskState.Cancelled } },
{ HelpdeskState.Resolved, new() { HelpdeskState.Closed, HelpdeskState.InProgress } },
{ HelpdeskState.Closed, new() { HelpdeskState.InProgress } }, // Reopen capability
{ HelpdeskState.Cancelled, new() { HelpdeskState.Created } } // Reinstate capability
};
public Result<HelpdeskState> TransitionState(HelpdeskState currentState, HelpdeskState targetState, HelpdeskTransitionContext context)
{
// Validate state transition
if (!_validTransitions.ContainsKey(currentState) ||
!_validTransitions[currentState].Contains(targetState))
{
return Result<HelpdeskState>.AsError($"Invalid state transition from {currentState} to {targetState}");
}
// Apply business rules for transition
var businessRuleResult = ValidateTransitionBusinessRules(currentState, targetState, context);
if (businessRuleResult.Status != ResultStatus.Success)
return Result<HelpdeskState>.FromResult(businessRuleResult);
// Execute transition logic
ExecuteTransitionLogic(currentState, targetState, context);
return Result<HelpdeskState>.AsSuccess(targetState);
}
private Result ValidateTransitionBusinessRules(HelpdeskState from, HelpdeskState to, HelpdeskTransitionContext context)
{
switch (to)
{
case HelpdeskState.Resolved:
if (string.IsNullOrEmpty(context.ResolutionNotes))
return Result.AsError("Resolution notes are required when resolving a ticket");
break;
case HelpdeskState.Assigned:
if (context.AssignedToUserId <= 0)
return Result.AsError("Must specify assigned user when assigning ticket");
break;
case HelpdeskState.Closed:
if (context.CustomerApproval == null || !context.CustomerApproval.Value)
return Result.AsError("Customer approval required before closing ticket");
break;
}
return Result.AsSuccess();
}
}
// Usage in Business Logic
public class HelpdeskBL : BaseBL
{
private readonly HelpdeskStateMachine _stateMachine = new();
public Result UpdateHelpdeskState(int helpdeskId, HelpdeskState targetState, HelpdeskTransitionContext context)
{
var helpdesk = GetHelpdesk(helpdeskId);
if (helpdesk.Status != ResultStatus.Success)
return Result.FromResult(helpdesk);
var transitionResult = _stateMachine.TransitionState(helpdesk.Data.State, targetState, context);
if (transitionResult.Status != ResultStatus.Success)
return Result.FromResult(transitionResult);
helpdesk.Data.State = transitionResult.Data;
helpdesk.Data.LastModified = DateTime.UtcNow;
return SaveHelpdesk(helpdesk.Data);
}
}
Business Calculation Patterns
1. Financial Calculation Engine
Complex Pricing Calculation Pattern
public class PricingCalculationEngine
{
public Result<PricingResult> CalculateComplexPricing(PricingRequest request)
{
var calculator = new PricingCalculator()
.WithBasePrice(request.Article.BasePrice)
.WithQuantityDiscounts(request.QuantityBreaks)
.WithCustomerDiscounts(request.Customer.DiscountTiers)
.WithRegionalPricing(request.DeliveryRegion)
.WithSeasonalAdjustments(request.CalculationDate)
.WithVatCalculation(request.VatRate);
return calculator.Calculate();
}
}
public class PricingCalculator
{
private decimal _basePrice;
private List<QuantityDiscount> _quantityDiscounts = new();
private List<CustomerDiscount> _customerDiscounts = new();
private RegionalPricing _regionalPricing;
private SeasonalAdjustment _seasonalAdjustment;
private decimal _vatRate;
public PricingCalculator WithBasePrice(decimal basePrice)
{
_basePrice = basePrice;
return this;
}
public Result<PricingResult> Calculate()
{
try
{
// Step 1: Apply quantity discounts
var netPrice = ApplyQuantityDiscounts(_basePrice);
// Step 2: Apply customer discounts
netPrice = ApplyCustomerDiscounts(netPrice);
// Step 3: Apply regional pricing adjustments
netPrice = ApplyRegionalAdjustments(netPrice);
// Step 4: Apply seasonal adjustments
netPrice = ApplySeasonalAdjustments(netPrice);
// Step 5: Calculate VAT
var vatAmount = netPrice * _vatRate;
var grossPrice = netPrice + vatAmount;
return Result<PricingResult>.AsSuccess(new PricingResult
{
BasePrice = _basePrice,
NetPrice = netPrice,
VatAmount = vatAmount,
GrossPrice = grossPrice,
DiscountApplied = _basePrice - netPrice,
CalculationBreakdown = GetCalculationBreakdown()
});
}
catch (Exception ex)
{
return Result<PricingResult>.AsError($"Pricing calculation failed: {ex.Message}");
}
}
}
2. Inventory Management Business Rules
Stock Level and Allocation Logic
public class InventoryBL : BaseBL
{
public Result<AllocationResult> AllocateInventory(AllocationRequest request)
{
// Business Rule: Check available stock
var stockResult = GetAvailableStock(request.ArticleId, request.WarehouseId);
if (stockResult.Status != ResultStatus.Success)
return Result<AllocationResult>.FromResult(stockResult);
var availableStock = stockResult.Data;
// Business Rule: Priority allocation logic
var allocationStrategy = DetermineAllocationStrategy(request.Priority);
var allocationResult = allocationStrategy.Allocate(availableStock, request);
if (allocationResult.AllocatedQuantity < request.RequestedQuantity)
{
// Business Rule: Handle partial allocation
var partialResult = HandlePartialAllocation(request, allocationResult);
if (partialResult.Status != ResultStatus.Success)
return Result<AllocationResult>.FromResult(partialResult);
}
// Business Rule: Update stock levels
var updateResult = UpdateStockLevels(request.ArticleId, request.WarehouseId, allocationResult.AllocatedQuantity);
if (updateResult.Status != ResultStatus.Success)
{
// Compensate allocation
CompensateAllocation(allocationResult);
return Result<AllocationResult>.FromResult(updateResult);
}
return Result<AllocationResult>.AsSuccess(allocationResult);
}
private IAllocationStrategy DetermineAllocationStrategy(AllocationPriority priority)
{
return priority switch
{
AllocationPriority.VipCustomer => new VipAllocationStrategy(),
AllocationPriority.LargeOrder => new BulkAllocationStrategy(),
AllocationPriority.Express => new ExpressAllocationStrategy(),
_ => new StandardAllocationStrategy()
};
}
}
Process Orchestration Patterns
1. Saga Pattern for Distributed Transactions
Order Processing Saga
public class OrderProcessingSaga
{
private readonly List<ISagaStep> _steps = new();
private readonly List<ISagaStep> _executedSteps = new();
public OrderProcessingSaga()
{
_steps.Add(new ValidateOrderStep());
_steps.Add(new ReserveInventoryStep());
_steps.Add(new ProcessPaymentStep());
_steps.Add(new CreateShipmentStep());
_steps.Add(new SendConfirmationStep());
}
public async Task<Result<OrderProcessingResult>> ExecuteAsync(OrderProcessingContext context)
{
foreach (var step in _steps)
{
try
{
var stepResult = await step.ExecuteAsync(context);
if (stepResult.Status != ResultStatus.Success)
{
await CompensateAsync();
return Result<OrderProcessingResult>.FromResult(stepResult);
}
_executedSteps.Add(step);
context.AddStepResult(step.Name, stepResult.Data);
}
catch (Exception ex)
{
await CompensateAsync();
return Result<OrderProcessingResult>.AsError($"Saga failed at step {step.Name}: {ex.Message}");
}
}
return Result<OrderProcessingResult>.AsSuccess(new OrderProcessingResult
{
OrderId = context.OrderId,
ProcessingSteps = _executedSteps.Select(s => s.Name).ToList(),
CompletedAt = DateTime.UtcNow
});
}
private async Task CompensateAsync()
{
// Execute compensation in reverse order
var stepsToCompensate = _executedSteps.AsEnumerable().Reverse();
foreach (var step in stepsToCompensate)
{
try
{
await step.CompensateAsync();
}
catch (Exception ex)
{
// Log compensation failure but continue
LogCompensationFailure(step.Name, ex);
}
}
}
}
// Example Saga Step Implementation
public class ProcessPaymentStep : ISagaStep
{
public string Name => "ProcessPayment";
public async Task<Result> ExecuteAsync(OrderProcessingContext context)
{
var paymentResult = await ProcessPayment(context.Order.PaymentInfo);
if (paymentResult.Status == ResultStatus.Success)
{
context.PaymentId = paymentResult.Data.PaymentId;
}
return paymentResult;
}
public async Task CompensateAsync()
{
// Reverse payment if possible
if (context.PaymentId.HasValue)
{
await RefundPayment(context.PaymentId.Value);
}
}
}
Rule Engine and Policy Patterns
1. Configurable Business Rules
Dynamic Rule Configuration
public class ConfigurableBusinessRuleEngine
{
private readonly Dictionary<string, IBusinessRule> _rules = new();
public void RegisterRule(string ruleId, IBusinessRule rule)
{
_rules[ruleId] = rule;
}
public Result<T> ApplyRules<T>(string context, T entity) where T : class
{
var applicableRules = GetApplicableRules(context);
foreach (var rule in applicableRules)
{
var result = rule.Apply(entity);
if (result.Status != ResultStatus.Success)
return Result<T>.FromResult(result);
entity = (T)result.Data;
}
return Result<T>.AsSuccess(entity);
}
private IEnumerable<IBusinessRule> GetApplicableRules(string context)
{
return _rules.Values.Where(r => r.AppliesTo(context));
}
}
// Example: Customer Validation Rules
public class CustomerCreditCheckRule : IBusinessRule<Customer>
{
private readonly decimal _minimumCreditScore;
public CustomerCreditCheckRule(decimal minimumCreditScore)
{
_minimumCreditScore = minimumCreditScore;
}
public bool AppliesTo(string context) => context == "CustomerApproval";
public Result<Customer> Apply(Customer customer)
{
if (customer.CreditScore < _minimumCreditScore)
{
customer.ApprovalStatus = ApprovalStatus.RequiresManualReview;
customer.ApprovalNotes = $"Credit score {customer.CreditScore} below minimum {_minimumCreditScore}";
}
else
{
customer.ApprovalStatus = ApprovalStatus.AutoApproved;
}
return Result<Customer>.AsSuccess(customer);
}
}
Business Rule Implementation Best Practices
1. Rule Separation and Organization
- Single Responsibility: Each rule class handles one specific business constraint
- Clear Naming: Rule names clearly indicate their purpose and scope
- Context-Aware: Rules know when and where they should be applied
- Composable: Rules can be combined to create complex validation chains
2. Error Handling and Messaging
- Descriptive Messages: Business rule violations provide clear, actionable error messages
- Localization: Error messages support multiple languages
- User-Friendly: Technical errors are translated to business-friendly language
- Audit Trail: All rule evaluations are logged for compliance and debugging
3. Performance Considerations
- Rule Caching: Expensive rule evaluations are cached when appropriate
- Lazy Evaluation: Rules are evaluated only when necessary
- Batch Processing: Multiple entities can be validated in batches
- Asynchronous Processing: Long-running rule evaluations use async patterns
Conclusion
The Centron application implements comprehensive business logic patterns that ensure:
- Consistency: All business rules are applied uniformly across the application
- Maintainability: Business logic is separated from infrastructure concerns
- Testability: Business rules can be tested in isolation
- Flexibility: New rules can be added without modifying existing code
- Performance: Rule evaluation is optimized for production use
- Compliance: All business operations are properly validated and audited
These patterns provide the foundation for reliable, scalable business logic that can evolve with changing business requirements while maintaining architectural integrity.