📜 ⬆️ ⬇️

Event Brokers Part 1



In general, in complex, dynamic systems it is very difficult to keep up with the changing organization of components, and if we are still somehow (this is in the 21st century!) Having solved the problem of creating components with the help of specialized containers, then the interaction with each other is still not completely subject to. For example, the response to events in .Net (and probably in other languages) was done at some very frivolous level. And naturally, in this task there appear all sorts of infrastructure solutions, which we will talk about.


Task


For most developers, especially developers who sit in outsourcing, responding to events (I mean mostly event s) either does not exist at all, or exists with terrible force, but in a very predictable way. For example, when someone clicks a button and a button_Click event is button_Click .
')
As soon as it is necessary to work closely with events, the game immediately stops. Here's an example - take a football player and coach:

public class FootballPlayer<br/>
{<br/>
public string Name { get;set; }<br/>
public void Score()<br/>
{<br/>
var e = Scored;<br/>
if (e != null ) e( this , new EventArgs());<br/>
}<br/>
public event EventHandler Scored; <br/>
}<br/>
public class FootballCoach<br/>
{<br/>
public void Watch(FootballPlayer p)<br/>
{<br/>
p.Scored += (_, __) =><br/>
{<br/>
Console.WriteLine( "I'm happy that {0} scored" , p.Name);<br/>
};<br/>
}<br/>
}<br/>

In this case, the subscription and notification work well:

var p = new FootballPlayer { Name = "Arshavin" };<br/>
var c = new FootballCoach();<br/>
c.Watch(p);<br/>
p.Score(); // coach saw it!

The whole joke here is that when you copy an object (whether through MemberwiseClone() or deep copy using BinaryFormatter ), all subscriptions of this object will be lost.

var p2 = c.Clone(); // deep copy :)
p.Score();<br/>

You can, of course, restore subscriptions with your hands or start using just a set of delegates or… instead of events. there, Expression<T> , something like that. But this is only part of the problem.

The next part of the problem is that at one point you will want your objects to subscribe to events automatically . For example, a player came out on the field - the coach begins to follow him. With “unsubscribing”, by the way, the same. If you exaggerate everything, you get something like the following:

class FootballCoach<br/>
{<br/>
public FootballCoach(FootballPlayer[] players)<br/>
{<br/>
foreach ( var p in players) {<br/>
p.EntersField += new EventHandler(StartWatchingPlayer);<br/>
p.LeavesField += new EventHandler(StopWatchingPlayer);<br/>
}<br/>
}<br/>
}<br/>

And so on until blue in the blue - in every StartXxx you will subscribe, in each EndXxx unsubscribe. But that's not all.

Imagine now that there are many such objects in the system. They all send messages to everyone else. If you make the pisces through += , we will get brutal connectedness and complete non-testability (and testing messages is generally difficult) of our code.

Well, finally, it’s necessary to put in a word “about a poor configuration”. After all, sometimes you want to receive notifications of a certain type , regardless of who sent them . For example, the judge doesn’t really care who curses about him - a player or a coach. (This is me, symbolically.) And, don't believe it, sometimes you want to make all kinds of tricky transformations, such as reacting to events in batches of 100 pieces, once an hour, only on Friday of the 13th full moon, etc. What is the current situation is not very predisposed.

Pub sub


An average developer immediately has a desire to write his event broker. And what, why not. We take and write a simple uncomplicated class:

public class EventBroker<br/>
{<br/>
private MultiDictionary< string , Delegate> subscriptions = <br/>
new MultiDictionary< string , Delegate>( true );<br/>
public void Publish<T>( string name, object sender, T args)<br/>
{<br/>
foreach ( var h in subscriptions[name])<br/>
h.DynamicInvoke(sender, args);<br/>
}<br/>
public void Subscribe<T>( string name, Delegate handler)<br/>
{<br/>
subscriptions.Add(name, handler);<br/>
}<br/>
}<br/>

You can poshamanit over thread safety (I would instinctively ReaderWriterLockSlim ), etc. but the essence will not change. We received a broker who can replace event subscriptions. Of course, you won’t get any QoS here, and you’ll have to write all the logic related to event sampling with pens, but there are already some moves - for example, by including the name as a classifier, we created a situation in which one class can sign a handler for several events at the same time.

The player no longer posts events at all.

public class FootballPlayer<br/>
{<br/>
private readonly EventBroker broker;<br/>
public string Name { get; set; }<br/>
public FootballPlayer(EventBroker broker)<br/>
{<br/>
this .broker = broker;<br/>
}<br/>
public void Injured()<br/>
{<br/>
broker.Publish( "LeavingField" , this , new PlayerInjuredEventArgs());<br/>
}<br/>
public void SentOff()<br/>
{<br/>
broker.Publish( "LeavingField" , this , new PlayerSentOffEventArgs());<br/>
}<br/>
}<br/>

Now the coach signs up through a broker:

public class FootballCoach<br/>
{<br/>
private readonly EventBroker broker;<br/>
public FootballCoach(EventBroker broker)<br/>
{<br/>
this .broker = broker;<br/>
}<br/>
public void Watch(FootballPlayer player)<br/>
{<br/>
broker.Subscribe<EventArgs>( "LeavingField" ,<br/>
new EventHandler(PlayerIsLeavingField));<br/>
}<br/>
public void PlayerIsLeavingField( object sender, EventArgs args)<br/>
{<br/>
Console.WriteLine( "Where are you going, {0}?" ,<br/>
(sender as FootballPlayer).Name);<br/>
}<br/>
}<br/>

Here we hope for polymorphism, due to the fact that according to the canon, all inherit arguments from EventArgs . Strong typing is not critical here. you can always do a cast. Here's how it will look like:

var uc = new UnityContainer();<br/>
uc.RegisterType<EventBroker>(<br/>
new ContainerControlledLifetimeManager());<br/>
var p = uc.Resolve<FootballPlayer>();<br/>
p.Name = "Arshavin" ;<br/>
var c = uc.Resolve<FootballCoach>();<br/>
<br/>
c.Watch(p);<br/>
<br/>
p.Injured();<br/>
p.SentOff();<br/>

What may surprise so much is the fact that events, in fact, were replaced by messenger. If you like me playing a lot with NServiceBus and other systems, then of course this will seem like a completely natural metamorphosis.

In the example above, the broker is registered in the container as singleton, so both the player and the trainer will receive the same copy. Here I actually do not quite subtle hint that in static scenarios, when it is known who subscribes to what, subscriptions can be described declaratively, ie:

public class FootballCoach<br/>
{<br/>
[SubscribesTo( "PlayerIsLeaving" )]<br/>
public void PlayerIsLeavingField( object sender, EventArgs args)<br/>
{<br/>
Console.WriteLine( "Where are you going, {0}?" ,<br/>
(sender as FootballPlayer).Name);<br/>
}<br/>
}<br/>

How this is done can be viewed in the next part of this post, and for those who can not stand it, I advise you to look here . The information is probably a little outdated, but I think the principle itself is clear.

Interim conclusion


The solution given here is not final for a number of reasons. First, it’s somehow non-kosher to have an explicit connection of components with a broker - it turns out that it needs to be thrown into each of the classes, and explicitly used in these classes. This problem, as we will see in the next part, is quite simply solved.

The second problem, about which we have already spoken, is that the description of some logic in event processing is now limited to one criterion - a string literal that works with something like a “classifier”. Limiting logic with similar methods is stupid, especially due to the presence of such a powerful tool as LINQ. (I think the hint is understood.)

Source: https://habr.com/ru/post/97285/


All Articles