Files
Masterarbeit/Versuche/Versuch 03/Ergenisse/software/Business_Rules.md
2026-02-19 11:21:18 +01:00

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.