-
-
Notifications
You must be signed in to change notification settings - Fork 19
feat(core): Add programmatic dependency declaration support #1889
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 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -155,7 +155,8 @@ public void InitializeModules(IEnumerable<IModule> modules) | |
| } | ||
|
|
||
| // Use the overload that includes dynamic dependencies from registration events | ||
| var dependencies = ModuleDependencyResolver.GetAllDependencies(moduleType, availableModuleTypes, _dependencyRegistry); | ||
| // and programmatic dependencies from DeclareDependencies method | ||
| var dependencies = ModuleDependencyResolver.GetAllDependencies(state.Module, availableModuleTypes, _dependencyRegistry); | ||
|
|
||
| foreach (var (dependencyType, ignoreIfNotRegistered) in dependencies) | ||
| { | ||
|
|
@@ -202,7 +203,9 @@ public void AddModule(IModule module) | |
| { | ||
| // Resolve dependencies inside write lock to prevent race conditions | ||
| // where _moduleStates could change between resolution and processing | ||
| var dependencies = ModuleDependencyResolver.GetDependencies(moduleType); | ||
| var dependencies = ConcatDependencies( | ||
| ModuleDependencyResolver.GetDependencies(moduleType), | ||
| ModuleDependencyResolver.GetProgrammaticDependencies(module)); | ||
|
|
||
| foreach (var (dependencyType, ignoreIfNotRegistered) in dependencies) | ||
| { | ||
|
|
@@ -590,4 +593,22 @@ private bool CanExecuteRespectingConstraints(ModuleState moduleState) | |
| // Delegate constraint checking to the evaluator | ||
| return _constraintEvaluator.CanQueue(moduleState, activeModules); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Concatenates two dependency enumerables into a single sequence. | ||
| /// </summary> | ||
| private static IEnumerable<(Type DependencyType, bool IgnoreIfNotRegistered)> ConcatDependencies( | ||
| IEnumerable<(Type DependencyType, bool IgnoreIfNotRegistered)> first, | ||
| IEnumerable<(Type DependencyType, bool IgnoreIfNotRegistered)> second) | ||
| { | ||
| foreach (var dep in first) | ||
| { | ||
| yield return dep; | ||
| } | ||
|
|
||
| foreach (var dep in second) | ||
| { | ||
| yield return dep; | ||
| } | ||
| } | ||
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| using System.Text.Json.Serialization; | ||
|
|
||
| namespace ModularPipelines.Enums; | ||
|
|
||
| /// <summary> | ||
| /// Defines the type of dependency between modules. | ||
| /// </summary> | ||
| [JsonConverter(typeof(JsonStringEnumConverter<DependencyType>))] | ||
| public enum DependencyType | ||
| { | ||
| /// <summary> | ||
| /// Required dependency. The dependent module will fail if this dependency is not registered. | ||
| /// </summary> | ||
| Required = 0, | ||
|
|
||
| /// <summary> | ||
| /// Optional dependency. The dependent module will not fail if this dependency is not registered. | ||
| /// </summary> | ||
| Optional = 1, | ||
|
|
||
| /// <summary> | ||
| /// Lazy dependency. The dependency is only executed if explicitly awaited within the module. | ||
| /// </summary> | ||
| Lazy = 2, | ||
|
|
||
| /// <summary> | ||
| /// Conditional dependency. The dependency is only active if a condition is met. | ||
| /// </summary> | ||
| Conditional = 3, | ||
| } | ||
|
Comment on lines
+21
to
+34
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using ModularPipelines.Enums; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace ModularPipelines.Models; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Represents a dependency declared programmatically via <see cref="Modules.IDependencyDeclaration"/>. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="DependencyType">The type of the module being depended on.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="Type">The type of dependency (Required, Optional, Lazy, Conditional).</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="IgnoreIfNotRegistered">Whether to ignore this dependency if not registered.</param> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public readonly record struct DeclaredDependency( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Type DependencyType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| DependencyType Type, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool IgnoreIfNotRegistered) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Creates a required dependency. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static DeclaredDependency Required(Type type) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new(type, Enums.DependencyType.Required, false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Creates an optional dependency. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static DeclaredDependency Optional(Type type) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new(type, Enums.DependencyType.Optional, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Creates a lazy dependency. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static DeclaredDependency Lazy(Type type) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new(type, Enums.DependencyType.Lazy, true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Creates a conditional dependency. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public static DeclaredDependency Conditional(Type type) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new(type, Enums.DependencyType.Conditional, false); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <param name="DependencyType">The type of the module being depended on.</param> | |
| /// <param name="Type">The type of dependency (Required, Optional, Lazy, Conditional).</param> | |
| /// <param name="IgnoreIfNotRegistered">Whether to ignore this dependency if not registered.</param> | |
| public readonly record struct DeclaredDependency( | |
| Type DependencyType, | |
| DependencyType Type, | |
| bool IgnoreIfNotRegistered) | |
| { | |
| /// <summary> | |
| /// Creates a required dependency. | |
| /// </summary> | |
| public static DeclaredDependency Required(Type type) => | |
| new(type, Enums.DependencyType.Required, false); | |
| /// <summary> | |
| /// Creates an optional dependency. | |
| /// </summary> | |
| public static DeclaredDependency Optional(Type type) => | |
| new(type, Enums.DependencyType.Optional, true); | |
| /// <summary> | |
| /// Creates a lazy dependency. | |
| /// </summary> | |
| public static DeclaredDependency Lazy(Type type) => | |
| new(type, Enums.DependencyType.Lazy, true); | |
| /// <summary> | |
| /// Creates a conditional dependency. | |
| /// </summary> | |
| public static DeclaredDependency Conditional(Type type) => | |
| new(type, Enums.DependencyType.Conditional, false); | |
| /// <param name="ModuleType">The type of the module being depended on.</param> | |
| /// <param name="DependencyKind">The type of dependency (Required, Optional, Lazy, Conditional).</param> | |
| /// <param name="IgnoreIfNotRegistered">Whether to ignore this dependency if not registered.</param> | |
| public readonly record struct DeclaredDependency( | |
| Type ModuleType, | |
| DependencyType DependencyKind, | |
| bool IgnoreIfNotRegistered) | |
| { | |
| /// <summary> | |
| /// Backwards-compatible alias for <see cref="ModuleType"/>. | |
| /// </summary> | |
| [Obsolete("Use ModuleType instead.")] | |
| public Type DependencyType => ModuleType; | |
| /// <summary> | |
| /// Backwards-compatible alias for <see cref="DependencyKind"/>. | |
| /// </summary> | |
| [Obsolete("Use DependencyKind instead.")] | |
| public DependencyType Type => DependencyKind; | |
| /// <summary> | |
| /// Creates a required dependency. | |
| /// </summary> | |
| public static DeclaredDependency Required(Type moduleType) => | |
| new(moduleType, Enums.DependencyType.Required, false); | |
| /// <summary> | |
| /// Creates an optional dependency. | |
| /// </summary> | |
| public static DeclaredDependency Optional(Type moduleType) => | |
| new(moduleType, Enums.DependencyType.Optional, true); | |
| /// <summary> | |
| /// Creates a lazy dependency. | |
| /// </summary> | |
| public static DeclaredDependency Lazy(Type moduleType) => | |
| new(moduleType, Enums.DependencyType.Lazy, true); | |
| /// <summary> | |
| /// Creates a conditional dependency. | |
| /// </summary> | |
| public static DeclaredDependency Conditional(Type moduleType) => | |
| new(moduleType, Enums.DependencyType.Conditional, false); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| using ModularPipelines.Enums; | ||
| using ModularPipelines.Models; | ||
|
|
||
| namespace ModularPipelines.Modules; | ||
|
|
||
| /// <summary> | ||
| /// Implementation of <see cref="IDependencyDeclaration"/> for collecting programmatic dependencies. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// <para> | ||
| /// <b>Thread Safety:</b> This class is not thread-safe. It is designed to be used | ||
| /// during the synchronous <see cref="Module{T}.DeclareDependencies"/> method call | ||
| /// and should not be accessed from multiple threads. | ||
| /// </para> | ||
| /// </remarks> | ||
| internal sealed class DependencyDeclaration : IDependencyDeclaration | ||
| { | ||
| private readonly List<DeclaredDependency> _dependencies = new(); | ||
|
|
||
| /// <summary> | ||
| /// Gets the declared dependencies. | ||
| /// </summary> | ||
| public IReadOnlyList<DeclaredDependency> Dependencies => _dependencies; | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOn<TModule>() where TModule : IModule | ||
| { | ||
| return DependsOn(typeof(TModule)); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOn(Type moduleType) | ||
| { | ||
| ValidateModuleType(moduleType); | ||
| _dependencies.Add(DeclaredDependency.Required(moduleType)); | ||
| return this; | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnOptional<TModule>() where TModule : IModule | ||
| { | ||
| return DependsOnOptional(typeof(TModule)); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnOptional(Type moduleType) | ||
| { | ||
| ValidateModuleType(moduleType); | ||
| _dependencies.Add(DeclaredDependency.Optional(moduleType)); | ||
| return this; | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnIf<TModule>(bool condition) where TModule : IModule | ||
| { | ||
| return DependsOnIf(typeof(TModule), condition); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnIf<TModule>(Func<bool> predicate) where TModule : IModule | ||
| { | ||
| ArgumentNullException.ThrowIfNull(predicate); | ||
| return DependsOnIf(typeof(TModule), predicate()); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnIf(Type moduleType, bool condition) | ||
| { | ||
| if (!condition) | ||
| { | ||
| return this; | ||
| } | ||
|
|
||
| ValidateModuleType(moduleType); | ||
| _dependencies.Add(DeclaredDependency.Conditional(moduleType)); | ||
| return this; | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnIf(Type moduleType, Func<bool> predicate) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(predicate); | ||
| return DependsOnIf(moduleType, predicate()); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnLazy<TModule>() where TModule : IModule | ||
| { | ||
| return DependsOnLazy(typeof(TModule)); | ||
| } | ||
|
|
||
| /// <inheritdoc /> | ||
| public IDependencyDeclaration DependsOnLazy(Type moduleType) | ||
| { | ||
| ValidateModuleType(moduleType); | ||
| _dependencies.Add(DeclaredDependency.Lazy(moduleType)); | ||
| return this; | ||
| } | ||
|
|
||
| private static void ValidateModuleType(Type moduleType) | ||
| { | ||
| ArgumentNullException.ThrowIfNull(moduleType); | ||
|
|
||
| if (!moduleType.IsAssignableTo(typeof(IModule))) | ||
| { | ||
| throw new ArgumentException( | ||
| $"{moduleType.FullName} is not a Module (does not implement IModule)", | ||
| nameof(moduleType)); | ||
| } | ||
| } | ||
| } |
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.
Using reflection with
GetMethodon every call toGetProgrammaticDependenciescould impact performance in pipelines with many modules. Consider caching theMethodInfoin aConcurrentDictionary<Type, MethodInfo?>keyed by module type to avoid repeated reflection lookups.Additionally, since
GetDeclaredDependenciesis an internal method defined onModule<T>, you could instead directly call it through a cast or interface rather than using reflection, which would be both faster and type-safe.