# Integration Patterns and Communication - Centron Enterprise Application ## Overview This document provides comprehensive coverage of integration patterns and implementations within the Centron .NET 8 enterprise application. Integration patterns encompass external API integration, data transformation, error handling, retry mechanisms, circuit breakers, fault tolerance, message patterns, and service orchestration that enable seamless communication between systems. ## Integration Architecture ### **Multi-Channel Integration Strategy** The Centron application supports multiple integration channels and patterns: 1. **REST API Integration**: HTTP-based services for modern web APIs 2. **SOAP Web Services**: Legacy system integration and enterprise services 3. **File-based Integration**: EDI, CSV, and XML file processing 4. **Database Integration**: Direct database connections and synchronization 5. **Message Queue Integration**: Asynchronous messaging and event processing 6. **Email Integration**: SMTP and email processing services ## External API Integration Patterns ### **1. REST Client Base Pattern** The foundation for all external REST API integrations: ```csharp public abstract class RestClientBase { protected readonly HttpClient _httpClient; protected readonly ILogger _logger; protected readonly RestClientCredentials _credentials; protected AccessToken _accessToken; protected RestClientBase(string baseUrl, RestClientCredentials credentials) { _credentials = credentials ?? throw new ArgumentNullException(nameof(credentials)); _logger = LogManager.GetCurrentClassLogger(); _httpClient = new HttpClient(new HttpClientHandler() { ServerCertificateCustomValidationCallback = ValidateCertificate }) { BaseAddress = new Uri(baseUrl), Timeout = TimeSpan.FromSeconds(30) }; // Set default headers _httpClient.DefaultRequestHeaders.Accept.Clear(); _httpClient.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); _httpClient.DefaultRequestHeaders.Add("User-Agent", "CentronClient/1.0"); } protected async Task> SendRequestAsync( HttpRequestMessage request, CancellationToken cancellationToken = default) where T : class { try { var response = await _httpClient.SendAsync(request, cancellationToken); return await ProcessResponseAsync(response); } catch (HttpRequestException ex) { _logger.Error(ex, "HTTP request failed: {Method} {Url}", request.Method, request.RequestUri); return Result.AsError($"HTTP request failed: {ex.Message}"); } catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException) { _logger.Error(ex, "Request timeout: {Method} {Url}", request.Method, request.RequestUri); return Result.AsError("Request timeout"); } catch (Exception ex) { _logger.Error(ex, "Unexpected error during request: {Method} {Url}", request.Method, request.RequestUri); return Result.AsError($"Request failed: {ex.Message}"); } } protected async Task> ProcessResponseAsync(HttpResponseMessage response) where T : class { var responseContent = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { var error = ParseErrorResponse(responseContent, response.StatusCode); _logger.Error("API request failed: {StatusCode} - {Error}", response.StatusCode, error); return Result.AsError($"API Error ({response.StatusCode}): {error}"); } try { if (typeof(T) == typeof(string)) { return Result.AsSuccess(responseContent as T); } var deserializedObject = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); return Result.AsSuccess(deserializedObject); } catch (JsonException ex) { _logger.Error(ex, "Failed to deserialize response: {Content}", responseContent); return Result.AsError($"Response deserialization failed: {ex.Message}"); } } protected async Task> GetJsonWebTokenAsync(string tokenEndpoint) { try { var tokenRequest = CreateTokenRequest(); var response = await _httpClient.SendAsync(tokenRequest); if (!response.IsSuccessStatusCode) { var error = await response.Content.ReadAsStringAsync(); return Result.AsError($"Token request failed: {error}"); } var tokenResponse = await response.Content.ReadAsStringAsync(); var token = JsonSerializer.Deserialize(tokenResponse); return Result.AsSuccess(token); } catch (Exception ex) { _logger.Error(ex, "Token acquisition failed"); return Result.AsError($"Token acquisition failed: {ex.Message}"); } } private HttpRequestMessage CreateTokenRequest() { var request = new HttpRequestMessage(HttpMethod.Post, "/oauth/token"); var parameters = new Dictionary { {"grant_type", "client_credentials"}, {"client_id", _credentials.ClientId}, {"client_secret", _credentials.ClientSecret} }; if (!string.IsNullOrEmpty(_credentials.Scope)) parameters["scope"] = _credentials.Scope; request.Content = new FormUrlEncodedContent(parameters); return request; } private bool ValidateCertificate(HttpRequestMessage request, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors errors) { // Implement certificate validation logic based on environment if (_credentials.AllowInvalidCertificates) return true; return errors == SslPolicyErrors.None; } protected virtual void Dispose(bool disposing) { if (disposing) { _httpClient?.Dispose(); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } // Specialized external API client implementation public class FinApiClient : RestClientBase, IFinApiClient { private readonly IOnlineBankingConnectionDialog _dialogHandler; private readonly bool _sandBoxMode; public FinApiClient(RestClientCredentials clientCredentials, bool sandBoxMode = false, IOnlineBankingConnectionDialog dialogHandler = null) : base(sandBoxMode ? FinApiConstants.SandboxAccessApiUrl : FinApiConstants.LiveAccessApiUrl, clientCredentials) { _dialogHandler = dialogHandler; _sandBoxMode = sandBoxMode; } public async Task> GetUserAccountAsync() { if (_accessToken == null || _accessToken.IsExpired()) return Result.AsError("No valid access token!"); _dialogHandler?.DisplayInfoMessage("Loading account data for the authorized user..."); var request = new HttpRequestMessage(HttpMethod.Get, FinApiConstants.UserAccountPath); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken.Token); var response = await SendRequestAsync(request); _dialogHandler?.DisplayInfoMessage($"Response: {response.Status}"); return response; } public async Task CreateUserAccountAsync(CreateUserAccountRequest createRequest) { try { // Get client token for user creation var tokenResult = await GetJsonWebTokenAsync(FinApiConstants.TokenPath); if (tokenResult.Status != ResultStatus.Success) return Result.FromResult(tokenResult); _dialogHandler?.DisplayInfoMessage("Creating user account..."); var request = new HttpRequestMessage(HttpMethod.Post, FinApiConstants.UserAccountPath); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResult.Data.Token); request.Content = new StringContent( JsonSerializer.Serialize(createRequest), Encoding.UTF8, "application/json"); var response = await _httpClient.SendAsync(request); var responseContent = await response.Content.ReadAsStringAsync(); if (response.StatusCode != HttpStatusCode.Created) { return Result.AsError($"User creation failed: {responseContent}"); } _dialogHandler?.DisplayInfoMessage("User account created successfully"); return Result.AsSuccess(); } catch (Exception ex) { return Result.AsError($"User account creation failed: {ex.Message}"); } } } ``` ### **2. Circuit Breaker Pattern for External Services** ```csharp public class CircuitBreaker { private readonly string _name; private readonly int _failureThreshold; private readonly TimeSpan _timeout; private readonly TimeSpan _recoveryTimeout; private readonly ILogger _logger; private CircuitBreakerState _state = CircuitBreakerState.Closed; private int _failureCount = 0; private DateTime _lastFailureTime = DateTime.MinValue; private readonly object _lock = new object(); public CircuitBreaker(string name, int failureThreshold, TimeSpan timeout, TimeSpan recoveryTimeout) { _name = name; _failureThreshold = failureThreshold; _timeout = timeout; _recoveryTimeout = recoveryTimeout; _logger = LogManager.GetCurrentClassLogger(); } public async Task> ExecuteAsync(Func>> operation) { if (_state == CircuitBreakerState.Open) { if (DateTime.UtcNow - _lastFailureTime < _recoveryTimeout) { _logger.Warn("Circuit breaker {Name} is OPEN - rejecting call", _name); return Result.AsError("Circuit breaker is open"); } // Transition to Half-Open state lock (_lock) { if (_state == CircuitBreakerState.Open) { _state = CircuitBreakerState.HalfOpen; _logger.Info("Circuit breaker {Name} transitioning to HALF-OPEN", _name); } } } try { var result = await operation(); if (result.Status == ResultStatus.Success) { OnSuccess(); } else { OnFailure(); } return result; } catch (Exception ex) { OnFailure(); return Result.AsError($"Operation failed: {ex.Message}"); } } private void OnSuccess() { lock (_lock) { _failureCount = 0; if (_state == CircuitBreakerState.HalfOpen) { _state = CircuitBreakerState.Closed; _logger.Info("Circuit breaker {Name} transitioning to CLOSED", _name); } } } private void OnFailure() { lock (_lock) { _failureCount++; _lastFailureTime = DateTime.UtcNow; if (_failureCount >= _failureThreshold && _state != CircuitBreakerState.Open) { _state = CircuitBreakerState.Open; _logger.Warn("Circuit breaker {Name} transitioning to OPEN after {FailureCount} failures", _name, _failureCount); } } } public CircuitBreakerState State => _state; public int FailureCount => _failureCount; } public enum CircuitBreakerState { Closed, Open, HalfOpen } // Circuit breaker integration with external service client public class ResilientExternalServiceClient { private readonly IExternalServiceClient _client; private readonly CircuitBreaker _circuitBreaker; private readonly IRetryPolicy _retryPolicy; public ResilientExternalServiceClient(IExternalServiceClient client) { _client = client; _circuitBreaker = new CircuitBreaker( name: "ExternalService", failureThreshold: 5, timeout: TimeSpan.FromSeconds(30), recoveryTimeout: TimeSpan.FromMinutes(1)); _retryPolicy = new ExponentialRetryPolicy( maxRetries: 3, baseDelay: TimeSpan.FromSeconds(1)); } public async Task> CallServiceAsync(Func>> serviceCall) { return await _circuitBreaker.ExecuteAsync(async () => { return await _retryPolicy.ExecuteAsync(serviceCall); }); } } ``` ### **3. Retry Policy Pattern** ```csharp public interface IRetryPolicy { Task> ExecuteAsync(Func>> operation); } public class ExponentialRetryPolicy : IRetryPolicy { private readonly int _maxRetries; private readonly TimeSpan _baseDelay; private readonly double _multiplier; private readonly TimeSpan _maxDelay; private readonly ILogger _logger; public ExponentialRetryPolicy( int maxRetries = 3, TimeSpan? baseDelay = null, double multiplier = 2.0, TimeSpan? maxDelay = null) { _maxRetries = maxRetries; _baseDelay = baseDelay ?? TimeSpan.FromSeconds(1); _multiplier = multiplier; _maxDelay = maxDelay ?? TimeSpan.FromMinutes(1); _logger = LogManager.GetCurrentClassLogger(); } public async Task> ExecuteAsync(Func>> operation) { var attempt = 0; var delay = _baseDelay; while (attempt <= _maxRetries) { try { var result = await operation(); if (result.Status == ResultStatus.Success || !IsRetriableError(result)) { if (attempt > 0) { _logger.Info("Operation succeeded after {Attempts} attempts", attempt + 1); } return result; } if (attempt < _maxRetries) { _logger.Warn("Operation failed (attempt {Attempt}/{MaxAttempts}): {Error}. Retrying in {Delay}ms", attempt + 1, _maxRetries + 1, result.Error, delay.TotalMilliseconds); await Task.Delay(delay); delay = TimeSpan.FromMilliseconds(Math.Min(delay.TotalMilliseconds * _multiplier, _maxDelay.TotalMilliseconds)); } attempt++; } catch (Exception ex) { if (attempt < _maxRetries && IsRetriableException(ex)) { _logger.Warn(ex, "Operation threw exception (attempt {Attempt}/{MaxAttempts}). Retrying in {Delay}ms", attempt + 1, _maxRetries + 1, delay.TotalMilliseconds); await Task.Delay(delay); delay = TimeSpan.FromMilliseconds(Math.Min(delay.TotalMilliseconds * _multiplier, _maxDelay.TotalMilliseconds)); attempt++; } else { return Result.AsError($"Operation failed after {attempt + 1} attempts: {ex.Message}"); } } } _logger.Error("Operation failed after {MaxAttempts} attempts", _maxRetries + 1); return Result.AsError($"Operation failed after {_maxRetries + 1} attempts"); } private bool IsRetriableError(Result result) { // Define which errors are worth retrying if (result.Error == null) return false; return result.Error.Contains("timeout") || result.Error.Contains("connection") || result.Error.Contains("network") || result.Error.Contains("503") || result.Error.Contains("502") || result.Error.Contains("504"); } private bool IsRetriableException(Exception exception) { return exception is TimeoutException || exception is HttpRequestException || exception is SocketException || (exception is TaskCanceledException && exception.InnerException is TimeoutException); } } // Polly-style retry policy for more advanced scenarios public class AdvancedRetryPolicy : IRetryPolicy { public async Task> ExecuteAsync(Func>> operation) { var retryPolicy = Policy .Handle() .Or() .Or() .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (outcome, timespan, retryCount, context) => { LogManager.GetCurrentClassLogger().Warn( "Retry {RetryCount} in {Delay}ms due to: {Exception}", retryCount, timespan.TotalMilliseconds, outcome.Exception?.Message); }); try { var result = await retryPolicy.ExecuteAsync(operation); return result; } catch (Exception ex) { return Result.AsError($"All retry attempts failed: {ex.Message}"); } } } ``` ## Data Transformation and Mapping Patterns ### **1. Entity-DTO Conversion Pattern** ```csharp public static class ObjectMapper { private static readonly IMapper _mapper; static ObjectMapper() { var configuration = new MapperConfiguration(cfg => { // Account mappings cfg.CreateMap() .ForMember(dest => dest.ContactPersons, opt => opt.MapFrom(src => src.ContactPersons)) .ForMember(dest => dest.Addresses, opt => opt.MapFrom(src => src.Addresses)); cfg.CreateMap() .ForMember(dest => dest.ContactPersons, opt => opt.MapFrom(src => src.ContactPersons)) .ForMember(dest => dest.Addresses, opt => opt.MapFrom(src => src.Addresses)); // Custom mappings with transformations cfg.CreateMap() .ForMember(dest => dest.TotalContracts, opt => opt.MapFrom(src => src.Contracts.Count)) .ForMember(dest => dest.ActiveContracts, opt => opt.MapFrom(src => src.Contracts.Count(c => c.IsActive))) .ForMember(dest => dest.LastActivity, opt => opt.MapFrom(src => src.LastModified)); // Complex nested mappings cfg.CreateMap() .ForMember(dest => dest.Items, opt => opt.MapFrom(src => src.ReceiptItems)) .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(src => src.Customer.CompanyName)) .ForMember(dest => dest.PaymentMethodName, opt => opt.MapFrom(src => src.PaymentMethod.Name)); }); configuration.AssertConfigurationIsValid(); _mapper = configuration.CreateMapper(); } public static TDestination Map(object source) { return _mapper.Map(source); } public static TDestination Map(TSource source, TDestination destination) { return _mapper.Map(source, destination); } public static IEnumerable Map(IEnumerable source) { return _mapper.Map>(source); } } // Custom conversion methods for complex transformations public static class ConversionExtensions { public static Account ConvertAccountDTOToAccount(AccountDTO accountDTO) { if (accountDTO == null) return null; var account = ObjectMapper.Map(accountDTO); // Custom logic that AutoMapper can't handle if (accountDTO.CustomFields != null) { account.CustomProperties = accountDTO.CustomFields .Select(cf => new CustomProperty { Name = cf.Key, Value = cf.Value, DataType = InferDataType(cf.Value) }) .ToList(); } // Handle special business logic if (account.CustomerType == CustomerType.Business && string.IsNullOrEmpty(account.VatNumber)) { throw new ValidationException("VAT number is required for business customers"); } return account; } public static AccountDTO ConvertAccountToAccountDTO(Account account) { if (account == null) return null; var accountDTO = ObjectMapper.Map(account); // Convert custom properties back to dictionary if (account.CustomProperties?.Any() == true) { accountDTO.CustomFields = account.CustomProperties .ToDictionary(cp => cp.Name, cp => cp.Value); } // Apply security filtering - don't expose sensitive data if (!HasPermissionToViewSensitiveData()) { accountDTO.BankAccountNumber = null; accountDTO.CreditLimit = null; } return accountDTO; } private static string InferDataType(object value) { return value switch { int or long => "Integer", decimal or double or float => "Decimal", bool => "Boolean", DateTime => "DateTime", _ => "String" }; } private static bool HasPermissionToViewSensitiveData() { // Implementation would check current user permissions return true; // Simplified for example } } ``` ### **2. External Data Format Transformation** ```csharp public class DataTransformationService { public async Task>> TransformExternalDataAsync( Stream inputStream, DataFormat inputFormat, IDataTransformationRules transformationRules) where T : class { try { var rawData = await ReadRawDataAsync(inputStream, inputFormat); var transformedData = new List(); foreach (var rawItem in rawData) { var transformResult = await transformationRules.TransformAsync(rawItem); if (transformResult.Status == ResultStatus.Success) { transformedData.Add(transformResult.Data); } else { LogTransformationError(rawItem, transformResult.Error); } } return Result>.AsSuccess(transformedData); } catch (Exception ex) { return Result>.AsError($"Data transformation failed: {ex.Message}"); } } private async Task>> ReadRawDataAsync(Stream inputStream, DataFormat format) { return format switch { DataFormat.Json => await ReadJsonDataAsync(inputStream), DataFormat.Xml => await ReadXmlDataAsync(inputStream), DataFormat.Csv => await ReadCsvDataAsync(inputStream), DataFormat.Edi => await ReadEdiDataAsync(inputStream), _ => throw new NotSupportedException($"Data format {format} is not supported") }; } private async Task>> ReadCsvDataAsync(Stream inputStream) { var data = new List>(); using var reader = new StreamReader(inputStream); var headerLine = await reader.ReadLineAsync(); if (headerLine == null) return data; var headers = headerLine.Split(',').Select(h => h.Trim()).ToArray(); string line; while ((line = await reader.ReadLineAsync()) != null) { var values = line.Split(','); var row = new Dictionary(); for (int i = 0; i < headers.Length && i < values.Length; i++) { row[headers[i]] = values[i].Trim(); } data.Add(row); } return data; } } // Transformation rules for specific data types public class CustomerTransformationRules : IDataTransformationRules { public async Task> TransformAsync(Dictionary rawData) { try { var customer = new CustomerDTO { CompanyName = GetStringValue(rawData, "company_name", "CompanyName"), CustomerNumber = GetIntValue(rawData, "customer_no", "CustomerNumber"), Email = GetStringValue(rawData, "email", "Email"), PhoneNumber = GetStringValue(rawData, "phone", "PhoneNumber") }; // Apply transformation rules customer.CompanyName = CleanCompanyName(customer.CompanyName); customer.Email = NormalizeEmail(customer.Email); customer.PhoneNumber = NormalizePhoneNumber(customer.PhoneNumber); // Validate transformed data var validationResult = await ValidateCustomer(customer); if (validationResult.Status != ResultStatus.Success) return Result.FromResult(validationResult); return Result.AsSuccess(customer); } catch (Exception ex) { return Result.AsError($"Customer transformation failed: {ex.Message}"); } } private string GetStringValue(Dictionary data, params string[] keys) { foreach (var key in keys) { if (data.TryGetValue(key, out var value)) return value?.ToString(); } return null; } private int GetIntValue(Dictionary data, params string[] keys) { foreach (var key in keys) { if (data.TryGetValue(key, out var value) && int.TryParse(value?.ToString(), out var intValue)) return intValue; } return 0; } private string CleanCompanyName(string companyName) { if (string.IsNullOrWhiteSpace(companyName)) return companyName; // Remove extra whitespace and normalize return Regex.Replace(companyName.Trim(), @"\s+", " "); } private string NormalizeEmail(string email) { if (string.IsNullOrWhiteSpace(email)) return email; return email.Trim().ToLowerInvariant(); } private string NormalizePhoneNumber(string phoneNumber) { if (string.IsNullOrWhiteSpace(phoneNumber)) return phoneNumber; // Remove all non-digit characters except + var cleaned = Regex.Replace(phoneNumber, @"[^\d+]", ""); // Add default country code if missing if (!cleaned.StartsWith("+") && cleaned.Length >= 10) { cleaned = "+49" + cleaned; // Default to German numbers } return cleaned; } private async Task ValidateCustomer(CustomerDTO customer) { if (string.IsNullOrWhiteSpace(customer.CompanyName)) return Result.AsError("Company name is required"); if (customer.CustomerNumber <= 0) return Result.AsError("Valid customer number is required"); if (!string.IsNullOrEmpty(customer.Email) && !IsValidEmail(customer.Email)) return Result.AsError("Invalid email format"); return Result.AsSuccess(); } private bool IsValidEmail(string email) { try { var mailAddress = new MailAddress(email); return mailAddress.Address == email; } catch { return false; } } } ``` ## Message Queue and Event Processing Patterns ### **1. Event-Driven Integration Pattern** ```csharp public interface IEventPublisher { Task PublishAsync(T eventData, string eventType, Dictionary metadata = null) where T : class; } public interface IEventSubscriber { Task SubscribeAsync(string eventType, Func> handler) where T : class; Task UnsubscribeAsync(string eventType); } public class MessageQueueEventService : IEventPublisher, IEventSubscriber { private readonly IMessageQueue _messageQueue; private readonly IEventSerializer _serializer; private readonly ILogger _logger; private readonly Dictionary>>> _handlers = new(); public async Task PublishAsync(T eventData, string eventType, Dictionary metadata = null) where T : class { try { var message = new EventMessage { Id = Guid.NewGuid().ToString(), EventType = eventType, Data = _serializer.Serialize(eventData), Metadata = metadata ?? new Dictionary(), Timestamp = DateTime.UtcNow }; message.Metadata["ContentType"] = typeof(T).FullName; message.Metadata["Publisher"] = Environment.MachineName; await _messageQueue.PublishAsync(eventType, message); _logger.LogInformation("Published event {EventType} with ID {EventId}", eventType, message.Id); return Result.AsSuccess(); } catch (Exception ex) { _logger.LogError(ex, "Failed to publish event {EventType}", eventType); return Result.AsError($"Event publishing failed: {ex.Message}"); } } public async Task SubscribeAsync(string eventType, Func> handler) where T : class { try { // Wrap the typed handler var wrappedHandler = new Func>(async data => { if (data is T typedData) { return await handler(typedData); } return Result.AsError($"Invalid data type for event {eventType}"); }); if (!_handlers.ContainsKey(eventType)) { _handlers[eventType] = new List>>(); // Subscribe to the queue await _messageQueue.SubscribeAsync(eventType, ProcessEventMessage); } _handlers[eventType].Add(wrappedHandler); _logger.LogInformation("Subscribed to event {EventType}", eventType); return Result.AsSuccess(); } catch (Exception ex) { _logger.LogError(ex, "Failed to subscribe to event {EventType}", eventType); return Result.AsError($"Event subscription failed: {ex.Message}"); } } private async Task ProcessEventMessage(EventMessage message) { try { if (!_handlers.ContainsKey(message.EventType)) { _logger.LogWarning("No handlers registered for event type {EventType}", message.EventType); return Result.AsSuccess(); // Not an error, just no handlers } // Deserialize the data var contentType = message.Metadata.GetValueOrDefault("ContentType"); var dataType = Type.GetType(contentType); var eventData = _serializer.Deserialize(message.Data, dataType); // Process with all registered handlers var handlers = _handlers[message.EventType]; var processingTasks = handlers.Select(handler => ProcessWithHandler(handler, eventData, message)); var results = await Task.WhenAll(processingTasks); // Check if any handlers failed var failures = results.Where(r => r.Status != ResultStatus.Success).ToList(); if (failures.Any()) { _logger.LogError("Event processing failed for {EventType}: {Errors}", message.EventType, string.Join("; ", failures.Select(f => f.Error))); return Result.AsError($"Event processing failed: {failures.Count} handlers failed"); } _logger.LogInformation("Successfully processed event {EventType} with {HandlerCount} handlers", message.EventType, handlers.Count); return Result.AsSuccess(); } catch (Exception ex) { _logger.LogError(ex, "Unexpected error processing event {EventType}", message.EventType); return Result.AsError($"Event processing failed: {ex.Message}"); } } private async Task ProcessWithHandler(Func> handler, object eventData, EventMessage message) { try { return await handler(eventData); } catch (Exception ex) { _logger.LogError(ex, "Handler failed for event {EventType} (ID: {EventId})", message.EventType, message.Id); return Result.AsError($"Handler execution failed: {ex.Message}"); } } } // Usage example - Account events public class AccountEventPublisher { private readonly IEventPublisher _eventPublisher; public async Task NotifyAccountCreatedAsync(AccountDTO account) { var eventData = new AccountCreatedEvent { AccountId = account.I3D, CompanyName = account.CompanyName, CustomerNumber = account.CustomerNumber, CreatedBy = account.CreatedBy, CreatedDate = DateTime.UtcNow }; return await _eventPublisher.PublishAsync(eventData, "AccountCreated", new Dictionary { ["CustomerId"] = account.CustomerNumber.ToString(), ["Priority"] = "Normal" }); } public async Task NotifyAccountUpdatedAsync(AccountDTO oldAccount, AccountDTO newAccount) { var eventData = new AccountUpdatedEvent { AccountId = newAccount.I3D, OldValues = ObjectMapper.Map>(oldAccount), NewValues = ObjectMapper.Map>(newAccount), ModifiedBy = newAccount.ModifiedBy, ModifiedDate = DateTime.UtcNow }; return await _eventPublisher.PublishAsync(eventData, "AccountUpdated"); } } // Event handlers public class AccountEventHandler { private readonly IEmailService _emailService; private readonly IAuditService _auditService; public async Task HandleAccountCreatedAsync(AccountCreatedEvent eventData) { try { // Send welcome email await _emailService.SendWelcomeEmailAsync(eventData.AccountId); // Log audit entry await _auditService.LogBusinessActionAsync( "ACCOUNT_CREATED", "Account", eventData.AccountId, new Dictionary { ["CompanyName"] = eventData.CompanyName, ["CustomerNumber"] = eventData.CustomerNumber }); return Result.AsSuccess(); } catch (Exception ex) { return Result.AsError($"Failed to handle AccountCreated event: {ex.Message}"); } } } ``` ### **2. File-Based Integration Pattern** ```csharp public class FileIntegrationService { private readonly IFileProcessor _fileProcessor; private readonly IDataTransformationService _transformationService; private readonly ILogger _logger; public async Task> ProcessFileAsync( string filePath, FileIntegrationConfig config) { try { var result = new FileProcessingResult { FilePath = filePath, StartTime = DateTime.UtcNow, Config = config }; // Validate file exists and is readable if (!File.Exists(filePath)) { return Result.AsError($"File not found: {filePath}"); } // Create backup if required if (config.CreateBackup) { var backupPath = CreateBackupFile(filePath); result.BackupPath = backupPath; } // Process the file using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); switch (config.ProcessingMode) { case FileProcessingMode.Import: await ProcessImportFileAsync(fileStream, config, result); break; case FileProcessingMode.Export: await ProcessExportFileAsync(fileStream, config, result); break; case FileProcessingMode.Transform: await ProcessTransformFileAsync(fileStream, config, result); break; default: return Result.AsError($"Unsupported processing mode: {config.ProcessingMode}"); } // Archive processed file if required if (config.ArchiveAfterProcessing) { var archivePath = ArchiveFile(filePath, config.ArchiveDirectory); result.ArchivePath = archivePath; } result.EndTime = DateTime.UtcNow; result.Success = true; _logger.LogInformation("File processing completed: {FilePath} - {RecordsProcessed} records in {Duration}ms", filePath, result.RecordsProcessed, (result.EndTime - result.StartTime).TotalMilliseconds); return Result.AsSuccess(result); } catch (Exception ex) { _logger.LogError(ex, "File processing failed: {FilePath}", filePath); return Result.AsError($"File processing failed: {ex.Message}"); } } private async Task ProcessImportFileAsync( Stream fileStream, FileIntegrationConfig config, FileProcessingResult result) { var rawData = await _fileProcessor.ReadDataAsync(fileStream, config.FileFormat); foreach (var rawRecord in rawData) { try { var transformedRecord = await _transformationService .TransformAsync(rawRecord, config.TransformationRules); if (transformedRecord.Status == ResultStatus.Success) { var saveResult = await SaveRecordAsync(transformedRecord.Data, config); if (saveResult.Status == ResultStatus.Success) { result.RecordsProcessed++; } else { result.Errors.Add($"Failed to save record: {saveResult.Error}"); result.RecordsFailed++; } } else { result.Errors.Add($"Failed to transform record: {transformedRecord.Error}"); result.RecordsFailed++; } } catch (Exception ex) { result.Errors.Add($"Error processing record: {ex.Message}"); result.RecordsFailed++; } } } private string CreateBackupFile(string originalPath) { var directory = Path.GetDirectoryName(originalPath); var fileName = Path.GetFileNameWithoutExtension(originalPath); var extension = Path.GetExtension(originalPath); var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var backupPath = Path.Combine(directory, $"{fileName}_backup_{timestamp}{extension}"); File.Copy(originalPath, backupPath); return backupPath; } private string ArchiveFile(string filePath, string archiveDirectory) { if (!Directory.Exists(archiveDirectory)) { Directory.CreateDirectory(archiveDirectory); } var fileName = Path.GetFileName(filePath); var archivePath = Path.Combine(archiveDirectory, fileName); // Add timestamp if file already exists if (File.Exists(archivePath)) { var nameWithoutExt = Path.GetFileNameWithoutExtension(fileName); var extension = Path.GetExtension(fileName); var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); archivePath = Path.Combine(archiveDirectory, $"{nameWithoutExt}_{timestamp}{extension}"); } File.Move(filePath, archivePath); return archivePath; } } ``` ## Integration Best Practices and Guidelines ### **1. Error Handling and Recovery** - **Graceful Degradation**: System continues to function when external services are unavailable - **Retry Logic**: Implement exponential backoff for transient failures - **Circuit Breakers**: Prevent cascade failures in distributed systems - **Dead Letter Queues**: Handle messages that cannot be processed ### **2. Data Consistency and Synchronization** - **Idempotent Operations**: Operations can be safely repeated without side effects - **Event Sourcing**: Track all changes as events for audit and replay capability - **Eventual Consistency**: Accept temporary inconsistency for better performance - **Compensation Patterns**: Implement compensating actions for failed operations ### **3. Security and Authentication** - **API Keys and Tokens**: Secure external API authentication - **Certificate Validation**: Verify SSL/TLS certificates in production - **Data Encryption**: Encrypt sensitive data in transit and at rest - **Input Validation**: Validate all external data before processing ### **4. Monitoring and Observability** - **Integration Health Checks**: Monitor external service availability - **Performance Metrics**: Track response times and throughput - **Error Tracking**: Log and monitor integration failures - **Distributed Tracing**: Track requests across service boundaries ## Conclusion The Centron application implements comprehensive integration patterns that provide: - **Resilience**: Circuit breakers and retry policies ensure system stability - **Flexibility**: Multiple integration channels support various external systems - **Reliability**: Error handling and recovery mechanisms prevent data loss - **Scalability**: Asynchronous processing and message queues handle high volumes - **Maintainability**: Clear patterns and abstractions simplify integration management - **Security**: Authentication and validation protect against external threats - **Observability**: Comprehensive monitoring provides visibility into integration health These integration patterns enable seamless communication between the Centron application and external systems while maintaining reliability, security, and performance under various conditions.