DryIOC Event Aggregator - dryioc

I'm trying to Implement an event aggregator using DryIOC. I have an Event dispatcher as follows:
public class DryIocEventDispatcher : IEventDispatcher
{
private readonly IContainer _container;
public DryIocEventDispatcher(IContainer container)
{
_container = container;
}
public void Dispatch<TEvent>(TEvent eventToDispatch) where TEvent : EventArgs
{
foreach (var handler in _container.ResolveMany<IHandles<TEvent>>())
{
handler.Handle(eventToDispatch);
}
}
}
I have a number of classes that can handle events. Indicated by the following Interface:
public interface IHandles<T> where T : System.EventArgs
{
void Handle(T args);
}
The gist of it is, that when I call the event dispatcher dispatch method, and pass in a type that inherits from EventArgs. It grabs from the IOC Container, all the types that Implement IHandles<> and call the handle method on them.
An event type may be handled by multiple Services. And a service can handle multiple event types. e.g:
public class ScoringService : IHandles<ZoneDestroyedEventArgs>, IHandles<ZoneCreatedEventArgs>
{
public void Handle(ZoneDestroyedEventArgs args)
{
Console.WriteLine("Scoring Service Handled ZoneDestroyed Event");
}
public void Handle(ZoneCreatedEventArgs args)
{
Console.WriteLine("Scoring Service Handled ZoneCreated Event");
}
}
public class RenderingService : IHandles<ZoneDestroyedEventArgs>, IHandles<ZoneCreatedEventArgs>
{
public void Handle(ZoneDestroyedEventArgs args)
{
Console.WriteLine("Rendering Service Handled ZoneDestroyed Event");
}
public void Handle(ZoneCreatedEventArgs args)
{
Console.WriteLine("Rendering Service Handled ZoneCreated Event");
}
}
Services need to do other things as well as handle events (but might not have other interfaces as they are not required). Some services need to be a singleton, and the handling of events should respect the singleton registration. Thus a call to container.Resolve(IHandles<>) should return the Singleton type for that service and not make multiple instances. These services are gathering events from multiple sources and therefore need to maintain internal state before sending them off elsewhere. Therefore different eventhandlers calling different services need to be sent to the same underlying instance.
I would like to be able to add IHandles interfaces to any service, and have it picked up automatically without having to fiddle with IOC mappings every time. Ideally service types should be added using convention based mapping as well.
So far I've been working on this for two days. I gave up trying to achieve it with structuremap. Now I'm trying DryIOC - but finding it to be even more difficult to understand and get right.

It is pretty easy to do in DryIoc (I am an owner). Here I will speak about V2 RC version.
Given that you've replaced IContainer dependency with IResolver which is automatically injected:
var container = new Container();
container.Register<IEventDispatcher, DryIocEventDispatcher>();
container.RegisterMany<ScoringService>(Reuse.Singleton);
container.RegisterMany<RenderingService>();
var eventDispatcher = container.Resolve<IEventDispatcher>();
eventDispatcher.Dispatch(new ZoneDestroyedEventArgs());
eventDispatcher.Dispatch(new ZoneCreatedEventArgs());
RegisterMany will take care of handlers being reused as Singleton and will return the same instance for both Handles<> interfaces.
Additionally you may use RegisterMapping to add/map IHandles<> service to already registered implementation.
DryIoc has even more to help with EventAggregator implementation.
Also here is solution for problem similar to yours.
The gist with your working example.

Related

Container has different rules for .net 5 web app and test project

I have a small .net 5 WebApi app, using DryIoc. I am now trying to set up an xUnit test suite for this app, but the integration tests fail immediately because one of the registrations has multiple constructors, even though I am using the same rules for both containers and running the same registrations in the same order as the app.
The registration works fine for the app because the container has ConstructorWithResolvableArguments set for the factory method, but it's not being set from anywhere in our code. I know I can easily just add that rule to the container for the tests, but I don't understand why a container set up in the exact same way appears to have different rules, and I am concerned there may be other differences that could affect the tests.
In the app, the container is set up like so:
Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new DryIocServiceProviderFactory(Startup.CreateContainer()))
.ConfigureWebHostDefaults(...);
StartUp.cs
...
public static IContainer CreateContainer() => new Container(DiConfiguration.SetRules);
public void ConfigureContainer(IContainer container)
{
container.RegisterCoreDependencies();
// Site specific registrations
container.ConfigureDomain();
}
DiConfiguration.cs
public static class DiConfiguration
{
public static Rules SetRules(Rules rules)
{
rules = rules.WithAutoConcreteTypeResolution();
rules = rules.WithVariantGenericTypesInResolve();
return rules;
}
public static void RegisterCoreDependencies(this IContainer container)
{
// dependency registrations, not much to see here, nothing clever
// just basic registrations e.g.
container.Register<IFoo, Foo>();
}
In the test suite I am setting up the container like so:
public class Test
{
private object _sut;
public Test()
{
var container = new Container(DiConfiguration.SetRules); // <--- Same rules as the app
container.RegisterCoreDependencies(); // <--- fails here
container.ConfigureDomain();
_sut = container.Resolve<Bar>();
}
[Fact]
public void Some_Test_Here()
{
...
}
}
As you can see, all the container registration code is abstracted out into a shared library (the DiConfiguration class). The test is failing calling RegisterCoreDependencies. I don't understand why the rules are different between the two scenarios, is it perhaps something introduced by the DryIocServiceProviderFactory call in the app setting some extra defaults? (DryIocServiceProviderFactory is part of the DryIoc.Microsoft.DependencyInjection package)
The DryIocServiceProviderFactory will override the existing container rules to conform to MS.DI container. Here is the code: https://github.com/dadhi/DryIoc/blob/5e3f1f7edfe237f69ba33c9166d17e284ca4781a/src/DryIoc.Microsoft.DependencyInjection/DryIocAdapter.cs#L97
Here how the rules are overriden in detail:
private static Rules WithMicrosoftDependencyInjectionRules(Rules rules)
{
rules = rules.Clone(cloneMade: true);
rules._settings |= Settings.TrackingDisposableTransients;
rules._settings &= ~Settings.ThrowOnRegisteringDisposableTransient;
rules._settings &= ~Settings.VariantGenericTypesInResolvedCollection;
rules._factorySelector = SelectLastRegisteredFactory;
rules._made._factoryMethod = DryIoc.FactoryMethod.ConstructorWithResolvableArguments;
return rules;
}

How do I create an Actor that has non-serialisable dependencies on a remote node?

Suppose you have an Actor, MyActor, which depends on an object which cannot be serialised. Examples include:
a Jackson ObjectMapper, for manipulating Json
a service of some kind obtained from a DI container
The Props for such an actor might look like this in Java:
public static Props props(ObjectMapper m, SomeService s) {
return Props.create(new Creator<MyActor>() {
#Override
public MyActor create() throws Exception {
return new MyActor(m, s);
}
});
}
The dependencies are passed into the constructor of the Actor. The problem is that this will not work in a clustered environment: these objects are not serialisable so trying to create the actor on a remote node will fail.
How do we solve this problem without using static global state?
There can be different types of the solution, it depends on your needs.
You can wrap the service for example in the Cluster Singleton and then send the actor ref to it across the cluster, your actor props then will have signature like this:
public static Props props(ActorRef refToMapperWrapper, ActorRef refToServiceWrapper).
The other solution is to instantiate new service and object mapper on the node you need it. You then should send the objects needed to create Service/ObjectMapper (i.e. constructor args) between the nodes, so these objects should be serialised somehow.
The ObjectMapper should better be created on each of the nodes independently, its configuration however can be sent across the nodes.

Unit Testing, using properties to pass in an interface

Im reading "The art of unit testing" atm and im having some issues with using properties to pass in an interface. The book states the following: "If you want parameters to be optional, use property getters/setters, which is a better way of defining optional parameters than adding different constructors to the class for each dependency."
The code for the property example is as follows:
public class LogAnalyzer
{
private IExtensionManager manager;
public LogAnalyzer ()
{
manager = new FileExtensionManager();
}
public IExtensionManager ExtensionManager
{
get { return manager; }
set { manager = value; }
}
public bool IsValidLogFileName(string fileName)
{
return manager.IsValid(fileName);
}
}
[Test]
Public void
IsValidFileName_NameShorterThan6CharsButSupportedExtension_ReturnsFalse()
{
//set up the stub to use, make sure it returns true
...
//create analyzer and inject stub
LogAnalyzer log = new LogAnalyzer ();
log.ExtensionManager=someFakeManagerCreatedEarlier;
//Assert logic assuming extension is supported
...
}
When/how would i use this feature?? The only scenario i can think of (This is probably wrong!) is if i had two methods in one class,
Method1() retrieves the database connection string from the config file and contains some form of check on the retrieved string.
Method2() then connect to the database and returns some data. The check here could be that that returned data is not null?
In this case, to test Method1() i could declare a stub that implements the IExtensionManager Interface, where the stub has a string which should pass any error checks i have in method1().
For Method2(), i declare a stub which implements the interface, and declare a datatable which contains some data, in the stub class. id then use the properties to assign this to the private manager variable and then call Method2?
The above may be complete BS, so if it is, id appreciate it if someone would let me know and ill remove it.
Thanks
Property injection used to change object's behavior after it was created.
BTW your code is tight coupled to FileExtensionManager, which is concrete implementation of IExtensionManager. How you are going to test LogAnalyzer with default manager? Use constructor injection to provide dependencies to your objects - this will make them testable:
public LogAnalyzer (IExtensionManager manager)
{
this.manager = manager();
}

Should unit tests know about NHibernate?

I'm following on from a previous question. The answer I accepted involves using a generic IRepository to handle basic CRUD, wrapped with a domain specific IMovieRepository which delegates to the generic setup. A further detail involves having a WrapQueryInSession method on the generic IRepository:
IEnumerable<T> WrapQueryInSession(Func<ISession, IEnumerable<T>> query);
I was getting to implementation when I realized that this exposes the NHibernate ISession to consumers of the generic repository. NHibernate is otherwise fully contained in the IRepository implementation, but for that method signature.
This comes to the fore when I want to unit test MovieRepository, by having an IRepository, implemented in RepositoryFake, passed to the MovieRepository constructor:
protected override void BeforeEachTest()
{
_fixture = new MovieRepository(new RepositoryFake());
}
My test class has a private fake repository implementation:
private class RepositoryFake : IRepository<Movie>
{
...
public IEnumerable<Movie> WrapQueryInSession(Func<ISession, IEnumerable<Movie>> query)
{
...
}
...
}
The way this is set up, the test class, and any other consumer of an IRepository implementation, is made aware of the ISession from NHibernate, and thus NHibernate itself. This seems a case of a leaky abstraction.
Is there a better way to fully contain use of NHibernate within an IRepository implementation?
The idea from my answer to your previous question was that the generic IRepository is only known inside your infrastructure layer - it is not published outside of this. When you publish the ISession to the non-generic repositories they gain a very versatile interface, as they have access to the ISession for querying. The problem with not exposing the ISession is that your generic repository will either:
Limit your querying capabilities or
Have a whole host of different methods for querying (basically duplicating the interface of the ISession.
Seems a bit of a waste having NHibernate's querying interface hidden away inside a facade (which the generic Repository would be limited to).
IMO, if you choose nHibernate, you should leverage the power it gives you and live with the dependence through-out your infrastructure dll (including tests). Think of the generic IRepository interface as a helper interface to NHibernate to reduce the amount of duplicate code inside the repositories.
I agree with the principle that NHibernate should not be abstracted completely but I have found that the querying interface of NHibernate can be hidden away without too much hassle and that can happen by using Query objects.
Each query object should leverage the power the NHibernate provides (ISession, ICriteria, IQuery, etc) and can be executed by the implementation of the IRepository.
Having Query objects instead of methods in your repositories offers better testability and it would not require any references to NHibernate in your test classes.
Here is how the whole thing could look like:
public interface IRepository
{
ISession Session { get; }
TResult Query<TResult>(IQuery<TResult> query);
}
public class Repository : IRepository
{
public ISession Session
{
get { return /* Call the session factory to return an ISession */; }
}
public TResult Query<TResult>(IQuery<TResult> query)
{
return query.Execute(Session));
}
}
public interface IQuery<TResult>
{
TResult Execute(QueryContext context);
}
public abstract class Query<TResult> : IQuery<TResult>
{
public abstract TResult Execute(ISession session);
}
public class GetPeopleByName: IQuery<Person>
{
private readonly string _name;
public GetPeopleByName(string name)
{
_name = name;
}
public override IList<Person> Execute(ISeesion session)
{
var query = context.Session.CreateCriteria(typeof(Person))
.Add(Restrictions.Eq("Name", _name));
return query.List<Person>();
}
}
Then you can use the above like:
IRepository repository = /* Get somehow the implementation */
IList<Person> people = repository.Execute(new GetPeopleByName("Anna"));

How to test soft deletion event listner without setting up NHibernate Sessions

I have overridden the default NHibernate DefaultDeleteEventListener according to this source: http://nhibernate.info/blog/2008/09/06/soft-deletes.html
so I have
protected override void DeleteEntity(
IEventSource session,
object entity,
EntityEntry entityEntry,
bool isCascadeDeleteEnabled,
IEntityPersister persister,
ISet transientEntities)
{
if (entity is ISoftDeletable)
{
var e = (ISoftDeletable)entity;
e.DateDeleted = DateTime.Now;
CascadeBeforeDelete(session, persister, entity, entityEntry, transientEntities);
CascadeAfterDelete(session, persister, entity, transientEntities);
}
else
{
base.DeleteEntity(session, entity, entityEntry, isCascadeDeleteEnabled, persister, transientEntities);
}
}
How can I test only this piece of code, without configuring an NHIbernate Session?
You could subclass your event listener in your test code and provide a public method with the same signature as DeleteEntity, which simply calls the protected base implementation of DeleteEntity.
Mock the other dependencies, call the public method in the testable class and verify DateDeleted has been set.
I'm fairly certain you will not be able to test this without a properly configured session. However, you could config Nhibernate to use for instance SQLite with some dummy data in your tests.