-
Notifications
You must be signed in to change notification settings - Fork 22
Support custom health check registrations on Journal and Snapshot Builders #683
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved the builders to their own files because I was tired of having to go looking for them |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Text; | ||
| using Akka.Configuration; | ||
| using Akka.Hosting; | ||
| using Akka.Persistence.Journal; | ||
| using Akka.Util; | ||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
|
|
||
| namespace Akka.Persistence.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Used to help build journal configurations | ||
| /// </summary> | ||
| public sealed class AkkaPersistenceJournalBuilder | ||
| { | ||
| internal readonly string JournalId; | ||
| internal readonly AkkaConfigurationBuilder Builder; | ||
| internal readonly Dictionary<Type, HashSet<string>> Bindings = new Dictionary<Type, HashSet<string>>(); | ||
| internal readonly Dictionary<string, Type> Adapters = new Dictionary<string, Type>(); | ||
| internal readonly HashSet<AkkaHealthCheckRegistration> HealthCheckRegistrations = []; | ||
|
|
||
| public AkkaPersistenceJournalBuilder(string journalId, AkkaConfigurationBuilder builder) | ||
| { | ||
| JournalId = journalId; | ||
| Builder = builder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Uses the built-in journal health check on the Akka.Persistence.Journal. | ||
| /// </summary> | ||
| /// <param name="unHealthyStatus">Default status to return when the plugin reports <see cref="PersistenceHealthStatus.Unhealthy"/> | ||
| /// or <see cref="PersistenceHealthStatus.Degraded"/>. Defaults to degraded.</param> | ||
| /// <param name="name">Optional name to add to the health check.</param> | ||
| /// <param name="tags">Custom tags for the health check. If null, defaults to ["akka", "persistence", "journal"].</param> | ||
| /// <returns>The current builder instance for method chaining.</returns> | ||
| public AkkaPersistenceJournalBuilder WithHealthCheck(HealthStatus unHealthyStatus = HealthStatus.Degraded, | ||
| string? name = null, | ||
| IEnumerable<string>? tags = null) | ||
| { | ||
| var registration = AddDefaultHealthCheck(name, unHealthyStatus, tags); | ||
| HealthCheckRegistrations.Add(registration); | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// For Akka.Persistence plugins that have custom health checks (see https://github.com/akkadotnet/Akka.Hosting/issues/678) | ||
| /// </summary> | ||
| /// <param name="registration">The custom health check registration.</param> | ||
| /// <returns>The current builder instance for method chaining.</returns> | ||
| public AkkaPersistenceJournalBuilder WithCustomHealthCheck(AkkaHealthCheckRegistration registration) | ||
| { | ||
| HealthCheckRegistrations.Add(registration); | ||
| return this; | ||
| } | ||
|
|
||
| public AkkaPersistenceJournalBuilder AddEventAdapter<TAdapter>(string eventAdapterName, | ||
| IEnumerable<Type> boundTypes) where TAdapter : IEventAdapter | ||
| { | ||
| AddAdapter<TAdapter>(eventAdapterName, boundTypes); | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| public AkkaPersistenceJournalBuilder AddReadEventAdapter<TAdapter>(string eventAdapterName, | ||
| IEnumerable<Type> boundTypes) where TAdapter : IReadEventAdapter | ||
| { | ||
| AddAdapter<TAdapter>(eventAdapterName, boundTypes); | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| public AkkaPersistenceJournalBuilder AddWriteEventAdapter<TAdapter>(string eventAdapterName, | ||
| IEnumerable<Type> boundTypes) where TAdapter : IWriteEventAdapter | ||
| { | ||
| AddAdapter<TAdapter>(eventAdapterName, boundTypes); | ||
|
|
||
| return this; | ||
| } | ||
|
|
||
| private void AddAdapter<TAdapter>(string eventAdapterName, IEnumerable<Type> boundTypes) | ||
| { | ||
| Adapters[eventAdapterName] = typeof(TAdapter); | ||
| foreach (var t in boundTypes) | ||
| { | ||
| if (!Bindings.ContainsKey(t)) | ||
| Bindings[t] = new HashSet<string>(); | ||
| Bindings[t].Add(eventAdapterName); | ||
| } | ||
| } | ||
|
|
||
| private AkkaHealthCheckRegistration AddDefaultHealthCheck(string? name, HealthStatus unHealthyStatus, IEnumerable<string>? tags) | ||
| { | ||
| var pluginId = $"akka.persistence.journal.{JournalId}"; | ||
| var healthCheckTags = tags?.ToList() ?? ["akka", "persistence", "journal"]; | ||
| var registration = new AkkaHealthCheckRegistration( | ||
| name ?? pluginId, | ||
| new JournalHealthCheck(pluginId), | ||
| unHealthyStatus, | ||
| healthCheckTags); | ||
| return registration; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// INTERNAL API - Builds the HOCON and then injects it. | ||
| /// </summary> | ||
| internal void Build() | ||
| { | ||
| // add the health checks if specified - do this FIRST before any early returns | ||
| foreach(var hc in HealthCheckRegistrations) | ||
| Builder.WithHealthCheck(hc); | ||
|
|
||
| // useless configuration - don't bother. | ||
| if (Adapters.Count == 0 || Bindings.Count == 0) | ||
| return; | ||
|
|
||
| var adapters = new StringBuilder() | ||
| .Append($"akka.persistence.journal.{JournalId}").Append("{"); | ||
|
|
||
| AppendAdapters(adapters); | ||
|
|
||
| adapters.AppendLine("}"); | ||
|
|
||
| var finalHocon = ConfigurationFactory.ParseString(adapters.ToString()); | ||
| Builder.AddHocon(finalHocon, HoconAddMode.Prepend); | ||
| } | ||
|
|
||
| internal void AppendAdapters(StringBuilder sb) | ||
| { | ||
| // useless configuration - don't bother. | ||
| if (Adapters.Count == 0 || Bindings.Count == 0) | ||
| return; | ||
|
|
||
| sb.AppendLine("event-adapters {"); | ||
| foreach (var kv in Adapters) | ||
| { | ||
| sb.AppendLine($"{kv.Key} = \"{kv.Value.TypeQualifiedName()}\""); | ||
| } | ||
|
|
||
| sb.AppendLine("}").AppendLine("event-adapter-bindings {"); | ||
| foreach (var kv in Bindings) | ||
| { | ||
| sb.AppendLine($"\"{kv.Key.TypeQualifiedName()}\" = [{string.Join(",", kv.Value)}]"); | ||
| } | ||
|
|
||
| sb.AppendLine("}"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using Akka.Hosting; | ||
| using Microsoft.Extensions.Diagnostics.HealthChecks; | ||
|
|
||
| namespace Akka.Persistence.Hosting; | ||
|
|
||
| /// <summary> | ||
| /// Used to help build snapshot store configurations | ||
| /// </summary> | ||
| public sealed class AkkaPersistenceSnapshotBuilder | ||
| { | ||
| internal readonly string SnapshotStoreId; | ||
| internal readonly AkkaConfigurationBuilder Builder; | ||
| internal readonly HashSet<AkkaHealthCheckRegistration> HealthCheckRegistrations = []; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now we take a hashset of health check registrations, rather than a singular registration. |
||
|
|
||
| public AkkaPersistenceSnapshotBuilder(string snapshotStoreId, AkkaConfigurationBuilder builder) | ||
| { | ||
| SnapshotStoreId = snapshotStoreId; | ||
| Builder = builder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Uses the built-in snapshot store health check on the Akka.Persistence.SnapshotStore. | ||
| /// </summary> | ||
| /// <param name="unHealthyStatus">Default status to return when the plugin reports <see cref="PersistenceHealthStatus.Unhealthy"/> | ||
| /// or <see cref="PersistenceHealthStatus.Degraded"/>. Defaults to degraded.</param> | ||
| /// <param name="name">Optional name to add to the health check.</param> | ||
| /// <param name="tags">Custom tags for the health check. If null, defaults to ["akka", "persistence", "snapshot-store"].</param> | ||
| /// <returns>The current builder instance for method chaining.</returns> | ||
| public AkkaPersistenceSnapshotBuilder WithHealthCheck(HealthStatus unHealthyStatus = HealthStatus.Degraded, | ||
| string? name = null, | ||
| IEnumerable<string>? tags = null) | ||
| { | ||
| var registration = AddDefaultHealthCheck(name, unHealthyStatus, tags); | ||
| HealthCheckRegistrations.Add(registration); | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// For Akka.Persistence plugins that have custom health checks (see https://github.com/akkadotnet/Akka.Hosting/issues/678) | ||
| /// </summary> | ||
| /// <param name="registration">The custom health check registration.</param> | ||
| /// <returns>The current builder instance for method chaining.</returns> | ||
| public AkkaPersistenceSnapshotBuilder WithCustomHealthCheck(AkkaHealthCheckRegistration registration) | ||
| { | ||
| HealthCheckRegistrations.Add(registration); | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Backward-compatible overload for external plugins that use the 2-parameter version. | ||
| /// </summary> | ||
| internal AkkaHealthCheckRegistration AddHealthCheck(string? name, HealthStatus unHealthyStatus) | ||
| { | ||
| return AddDefaultHealthCheck(name, unHealthyStatus, tags: null); | ||
| } | ||
|
|
||
| internal AkkaHealthCheckRegistration AddDefaultHealthCheck(string? name, HealthStatus unHealthyStatus, IEnumerable<string>? tags) | ||
| { | ||
| var pluginId = $"akka.persistence.snapshot-store.{SnapshotStoreId}"; | ||
| var healthCheckTags = tags?.ToList() ?? ["akka", "persistence", "snapshot-store"]; | ||
| var registration = new AkkaHealthCheckRegistration( | ||
| name ?? pluginId, | ||
| new SnapshotStoreHealthCheck(pluginId), | ||
| unHealthyStatus, | ||
| healthCheckTags); | ||
| return registration; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// INTERNAL API - Registers health checks if configured. | ||
| /// </summary> | ||
| internal void Build() | ||
| { | ||
| // add the health checks if specified | ||
| foreach(var hc in HealthCheckRegistrations) | ||
| Builder.WithHealthCheck(hc); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New methods - they don't do anything all that functionally different than the main health check registration methods on the
AkkaConfigurationBuilder. This is a pure DX choice to support Akka.Persistence plugin authors.