| | | 1 | | namespace AsiBackbone.Core.Outbox; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Provides provider-neutral retry timing, poison-message, and optional claim/lease options for governance outbox drain |
| | | 5 | | /// </summary> |
| | | 6 | | /// <remarks> |
| | | 7 | | /// These options control default retry timestamps when a downstream emitter does not provide its own retry-after value. |
| | | 8 | | /// </remarks> |
| | | 9 | | public sealed class AsiBackboneGovernanceOutboxOptions |
| | | 10 | | { |
| | | 11 | | /// <summary> |
| | | 12 | | /// Gets the stable default reason code used when the drain dead-letters an entry after the configured retry thresho |
| | | 13 | | /// </summary> |
| | | 14 | | public const string DefaultDeadLetterReasonCode = "outbox.max_retry_attempts_exceeded"; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Gets the stable default reason message used when the drain dead-letters an entry after the configured retry thre |
| | | 18 | | /// </summary> |
| | | 19 | | public const string DefaultDeadLetterReasonMessage = "Governance outbox entry exceeded the configured maximum retry |
| | | 20 | | |
| | | 21 | | /// <summary> |
| | | 22 | | /// Gets or sets the default delay applied after a transient emission failure or unexpected emitter exception. |
| | | 23 | | /// </summary> |
| | 636 | 24 | | public TimeSpan RetryDelay { get; set; } = TimeSpan.FromMinutes(1); |
| | | 25 | | |
| | | 26 | | /// <summary> |
| | | 27 | | /// Gets or sets the default delay applied when an emitter returns a pending or deferred result without a retry-afte |
| | | 28 | | /// </summary> |
| | 631 | 29 | | public TimeSpan DeferredDelay { get; set; } = TimeSpan.FromMinutes(1); |
| | | 30 | | |
| | | 31 | | /// <summary> |
| | | 32 | | /// Gets or sets the maximum number of failed emission attempts permitted before the drain applies its poison-messag |
| | | 33 | | /// </summary> |
| | | 34 | | /// <remarks> |
| | | 35 | | /// The threshold counts the failure currently being processed. A value of <c>1</c> dead-letters the first failed at |
| | | 36 | | /// </remarks> |
| | 650 | 37 | | public int MaxRetryAttempts { get; set; } = 5; |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// Gets or sets a value indicating whether entries are dead-lettered when <see cref="MaxRetryAttempts" /> is reache |
| | | 41 | | /// </summary> |
| | 507 | 42 | | public bool DeadLetterOnMaxRetryAttempts { get; set; } = true; |
| | | 43 | | |
| | | 44 | | /// <summary> |
| | | 45 | | /// Gets or sets the provider-neutral reason code recorded when the configured retry threshold is reached. |
| | | 46 | | /// </summary> |
| | 609 | 47 | | public string DeadLetterReasonCode { get; set; } = DefaultDeadLetterReasonCode; |
| | | 48 | | |
| | | 49 | | /// <summary> |
| | | 50 | | /// Gets or sets the provider-neutral reason message recorded when the configured retry threshold is reached. |
| | | 51 | | /// </summary> |
| | 611 | 52 | | public string DeadLetterReasonMessage { get; set; } = DefaultDeadLetterReasonMessage; |
| | | 53 | | |
| | | 54 | | /// <summary> |
| | | 55 | | /// Gets or sets a value indicating whether the drain should claim outbox entries before provider emission when the |
| | | 56 | | /// </summary> |
| | 501 | 57 | | public bool UseClaimLeases { get; set; } |
| | | 58 | | |
| | | 59 | | /// <summary> |
| | | 60 | | /// Gets or sets the worker, process, node, or partition identifier used when <see cref="UseClaimLeases" /> is enabl |
| | | 61 | | /// </summary> |
| | 394 | 62 | | public string? ClaimWorkerId { get; set; } |
| | | 63 | | |
| | | 64 | | /// <summary> |
| | | 65 | | /// Gets or sets the lease duration used when <see cref="UseClaimLeases" /> is enabled. |
| | | 66 | | /// </summary> |
| | 711 | 67 | | public TimeSpan ClaimLeaseDuration { get; set; } = GovernanceOutboxClaimRequest.DefaultLeaseDuration; |
| | | 68 | | |
| | | 69 | | /// <summary> |
| | | 70 | | /// Validates the configured outbox retry, poison-message, timing, and claim options. |
| | | 71 | | /// </summary> |
| | | 72 | | /// <exception cref="InvalidOperationException">Thrown when a retry threshold, reason, delay, or claim lease option |
| | | 73 | | public void Validate() |
| | | 74 | | { |
| | 145 | 75 | | ValidateDelay(RetryDelay, nameof(RetryDelay)); |
| | 139 | 76 | | ValidateDelay(DeferredDelay, nameof(DeferredDelay)); |
| | | 77 | | |
| | 133 | 78 | | if (MaxRetryAttempts <= 0) |
| | | 79 | | { |
| | 2 | 80 | | throw new InvalidOperationException($"{nameof(MaxRetryAttempts)} must be greater than zero."); |
| | | 81 | | } |
| | | 82 | | |
| | 131 | 83 | | if (string.IsNullOrWhiteSpace(DeadLetterReasonCode)) |
| | | 84 | | { |
| | 2 | 85 | | throw new InvalidOperationException($"{nameof(DeadLetterReasonCode)} is required."); |
| | | 86 | | } |
| | | 87 | | |
| | 129 | 88 | | if (string.IsNullOrWhiteSpace(DeadLetterReasonMessage)) |
| | | 89 | | { |
| | 2 | 90 | | throw new InvalidOperationException($"{nameof(DeadLetterReasonMessage)} is required."); |
| | | 91 | | } |
| | | 92 | | |
| | 127 | 93 | | if (ClaimLeaseDuration <= TimeSpan.Zero) |
| | | 94 | | { |
| | 4 | 95 | | throw new InvalidOperationException($"{nameof(ClaimLeaseDuration)} must be greater than TimeSpan.Zero."); |
| | | 96 | | } |
| | | 97 | | |
| | 123 | 98 | | if (UseClaimLeases && string.IsNullOrWhiteSpace(ClaimWorkerId)) |
| | | 99 | | { |
| | 4 | 100 | | throw new InvalidOperationException($"{nameof(ClaimWorkerId)} is required when {nameof(UseClaimLeases)} is e |
| | | 101 | | } |
| | 119 | 102 | | } |
| | | 103 | | |
| | | 104 | | private static void ValidateDelay(TimeSpan delay, string propertyName) |
| | | 105 | | { |
| | 284 | 106 | | if (delay < TimeSpan.Zero) |
| | | 107 | | { |
| | 12 | 108 | | throw new InvalidOperationException($"{propertyName} must be greater than or equal to TimeSpan.Zero."); |
| | | 109 | | } |
| | 272 | 110 | | } |
| | | 111 | | } |