core modules knowntypes - grecosoft/NetFusion GitHub Wiki
This topic discusses the concept of Known Plugin Types used by modules to discover types defined by other plugins from which instances are created. Since Core plugins provide services used by other Core and Application specific plugins, this feature is most often used when implementing Core plugins. However, this feature can be used by all plugin types.
Since Core Plugins are most often used by application specific microservices, and tend to be generic, they must find types defined by other plugins (most often defined within Application plugins) providing application-specific information used by the Core plugin. For example, the NetFusion.Integration.ServiceBus plugin knows how to create topics and queues but does not know the details of the running application-specific microservice. The Service Bus plugin when bootstrapped, looks for all concrete types implementing NamespaceRouter for which instances are created. These application specific route classes specify for which queue or topic a given type of message is associated. As in this case, the information is often metadata cached by the core plugin used to implement its runtime functionality.
A know type is a contract defined by one Plugin for which other Plugins provided concrete implementations. When a microservice bootstraps, the Plugin defining the contract can scan for all concrete types implementing the defined contract. Plugin Known Types are defined by deriving from the IPluginKnownType marker interface.
Plugin Known Types are defined by deriving from the IPluginKnownType marker interface as is the case for the NamespaceRouter type. Most often, known-types are defined as interfaces for which implementations are provided by other plugins. These known-type declarations are defined within the plugin where they are consumed. The plugin defining an interface based know-type will often provide a base implementation from which other plug-ins can derive. For example, the Service Bus plugin provides a base abstract class named NamespaceRouter that other plugins can derive. These base classes often contain methods called to provide application-specific information. In the case of NamespaceRouter, it derives from BusRouterBase which implements IBusRouter defined as IPluginKnownType.
This topic will define an interface called IAllowedIpAddresses that will be used to specify a list of IP addresses that are valid. An implementation of this interface can be provided by any plugin choosing to provide a list a valid IP addresses.
The logic for this example will be placed within the Examples.Bootstrap.CrossCut project. Start by creating a new module at the following location:
Examples.Bootstrap.CrossCut/Plugin/Modules
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.CrossCut.Plugin.Modules;
public class ValidAddressModule : PluginModule
{
}
Add the ValidAddressModule to the CrossCutPlugin class so it will be bootstrapped.
Examples.Bootstrap.CrossCut/Plugin/CrossCutPlugin.cs
public class CrossCutPlugin : PluginBase
{
public override string PluginId => "7332C55B-E64C-430C-8836-488787CAC875";
public override PluginTypes PluginType => PluginTypes.CorePlugin;
public override string Name => "Cross-Cut Component";
public CrossCutPlugin()
{
AddModule<CoreModuleOne>();
AddModule<CoreModuleTwo>();
AddModule<ValidAddressModule>(); // <-- Add this line
Description = "Example of a core plugin";
}
}
Next, create an interface for a component, to be implemented by other plugins, used to provide a list of valid IP addresses at the following location:
Examples.Bootstrapping.CrossCut/
namespace Core.Component
{
public class AllowedAddresses
{
public string Source { get; set; } = "";
public string[] IpAddresses { get; set; } = Array.Empty<string>();
}
}
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.CrossCut;
public interface IAllowedIpAddresses : IPluginKnownType
{
AllowedAddresses ListAllowedAddresses();
}
The above defines the interface IAllowedIpAddresses deriving from IPluginKnownType. This will allow any type implementing this interface to be discovered by the ValidAddressModule as shown next.
Add a property declared as an enumerable of IAllowedIpAddresses to the module:
using Microsoft.Extensions.Logging;
using NetFusion.Common.Base;
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.CrossCut.Plugin.Modules;
public class ValidAddressModule : PluginModule
{
public IEnumerable<IAllowedIpAddresses> AllowedAddresses { get; private set; } = Array.Empty<IAllowedIpAddresses>();
public override void Initialize()
{
NfExtensions.Logger.Log<ValidAddressModule>(
LogLevel.Information, $"Number discovered implementations: {AllowedAddresses.Count()}");
}
}
Since the AllowedAddresses property is an enumerable of a type derived from IPluginKnownType, the NetFusion bootstrap process will automatically find all derived IAllowedIpAddresses concrete classes and populate the property with created instances. At this point, if you run the microservice project, zero components implementing this interface will be found:
cd ./src/Examples.Bootstrapping.WebApi/
dotnet run
This section will declare three concrete classes deriving from IAllowedIpAddresses. Once these classes are defined within other plug-in based projects, they will be atomically found by the ValidAddressModule. This example will add two such classes to the Examples.Bootstrapping.App project and one to the Examples.Bootstrapping.WebApi project.
Add the following files to the Examples.Bootstrapping.App project:
using Examples.Bootstrapping.CrossCut;
namespace Examples.Bootstrapping.App;
public class InternalIpAddresses : IAllowedIpAddresses
{
public AllowedAddresses ListAllowedAddresses() => new()
{
Source = $"App.Plugin.Source2[{nameof(InternalIpAddresses)}]",
IpAddresses = new []
{
"100.64.0.0",
"169.254.0.0"
}
};
}
using Examples.Bootstrapping.CrossCut;
namespace Examples.Bootstrapping.App;
public class ExternalIpAddresses : IAllowedIpAddresses
{
public AllowedAddresses ListAllowedAddresses() => new()
{
Source = $"App.Plugin.Source1[{nameof(ExternalIpAddresses)}]",
IpAddresses = new []
{
"192.88.105.0",
"192.87.122.0"
}
};
}
Then add the following class to the Examples.Bootstrapping.WebApi project:
using Examples.Bootstrapping.CrossCut;
namespace Examples.Bootstrapping.WebApi;
public class ExternalIpAddresses : IAllowedIpAddresses
{
public AllowedAddresses ListAllowedAddresses() => new()
{
Source = $"Host.Plugin.Source[{nameof(ExternalIpAddresses)}]",
IpAddresses = new []
{
"192.55.111.0",
"192.56.321.0"
}
};
}
After adding the above classes, run the WebApiHost again and 3 instances will now be found:
cd ./src/Examples.Bootstrapping.WebApi/
dotnet run
This is the first place where the type of plugin influences the bootstrap process. When NetFusion determines the concrete classes for a given known-type, the type of plugin in which the module is located is taken into consideration. The above ValidAddressModule class is defined within a Core plugin. Therefore, when the AllowedAddresses property is populated, NetFusion looks within all Plugins for types: Host, Application, and Core for concrete implementations. Below are the rules used to determine which plugins are searched:
Plug-in Type Containing Module | Plug-in Types Searched | Note |
---|---|---|
Core | Core, Application, Host | Core plugins provide infrastructure based components used by other plugins. Therefore, types contained within all plugins are searched for IPluginKnownType implementations. |
Application | Application, Host | Since modules contained within Application plugins contain application-specific implementations, types searches are limited to other Application plugins and the Host. |
Host | Host | Since Host plugins are a specific type of application plugin, responsible for running the microservice, searches are limited to types only it defines. |
The type of plug-in (Application, Host, or Core) determines the plug-ins from which the know-type implementation are searched and instantiated. If the plugin is a Core, such as the NetFusion.Infrastructure.ServiceBus plugin, all plugins registered with the composite-container are searched. However, if the plugin is an application centric, only other registered application-centric plugins are searched. This is by design since a core plugin provides services that can be used by all plugin types. However, an application-centric plugin never provides services used outside of a given microservice.
The following will declare a plug-in module service allowing other plugins and application-components to check if a given IP address is allowed. Creating a Plugin Module Service was discussed within the last topic. First define a contract for the service.
Examples.Bootstrapping.CrossCut/Plugin
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.CrossCut.Plugin;
public interface IAddressValidation : IPluginModuleService
{
string? IsValidAddress(string ip);
}
Next, implement the interface within the ValidAddressModule already having a reference to all the found IAllowedIpAddresses instances:
using Microsoft.Extensions.Logging;
using NetFusion.Common.Base;
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.CrossCut.Plugin.Modules;
public class ValidAddressModule : PluginModule,
IAddressValidation
{
public IEnumerable<IAllowedIpAddresses> AllowedAddresses { get; private set; } = Array.Empty<IAllowedIpAddresses>();
private AllowedAddresses[] _addresses = Array.Empty<AllowedAddresses>();
public override void Initialize()
{
NfExtensions.Logger.Log<ValidAddressModule>(
LogLevel.Information, $"Number discovered implementations: {AllowedAddresses.Count()}");
_addresses = AllowedAddresses.Select(a => a.ListAllowedAddresses()).ToArray();
}
public string? IsValidAddress(string ip)
{
var address = _addresses.FirstOrDefault(a => a.IpAddresses.Contains(ip));
return address?.Source;
}
}
As discussed within the Service topic, any service contract deriving from IPluginModuleService can be referenced as follows:
- By declaring a public settable property of the service interface on another module
- Injected into any component registered within the DI Container
This example will define a WebApi controller injecting the service and exposing the functionality as an API.
Examples.Bootstrapping.WebApi/Controllers/AddressController.cs
using Examples.Bootstrapping.CrossCut.Plugin;
using Microsoft.AspNetCore.Mvc;
namespace Examples.Bootstrapping.WebApi.Controllers;
[ApiController, Microsoft.AspNetCore.Components.Route("api/addresses")]
public class AddressController : ControllerBase
{
private readonly IAddressValidation _addressValidation;
public AddressController(IAddressValidation addressValidation)
{
_addressValidation = addressValidation;
}
[HttpGet("allowed/{ip}")]
public IActionResult CheckAllowedAddress([FromRoute]string ip)
{
var validatedBySource = _addressValidation.IsValidAddress(ip);
if (validatedBySource == null)
{
return BadRequest();
}
return Ok(validatedBySource);
}
}
Lastly, run the service and invoke the WebApi controller method for a valid and invalid IP address:
cd ./src/Examples.Bootstrapping.WebApi/
dotnet run
The last two topics discussed the automatic setting of Plugin Module defined properties to services and known-type instances. The remaining topics will discuss registering additional services within the DI Containers, Logging, and the last phase where all modules are executed.