I am having a problem modeling and implementing a event attendance system using CQRS. My issue is that a child entity can raise an event, but I am not sure how and when to process it.
Basically, an event can have attendees, which start in a TBD state, and can either accept or reject attending the event. However, they can change their attendance, and when that happens, I would like a event to be raised so that an event handler can process (notify a event organizer for example).
I have used the state pattern to manage an attendee's state, and it depend on the current state whether a event should be raised. At the moment, this event does not change the state of the Event. However it seems to me that this event should be part of the event stream.
My issue is that I don't know if the event will be raised until I apply one of the AttendeeResponded events, which calls the method on the current state.If I raise an event during a Apply, then I would end up with problem rehydrating the AR. I could add this information to the event during the apply, having the state return information, but then the event become mutable.
My thought is that maybe the state pattern does not work well as a place where events could be generated, or that maybe that the state pattern is not a good fit here. I could extend the state to have a method that determines if a certain state change will throw an event, but that seems clunky.
Finally, my AR's don't have any references to eventBus's, so I can't just throw an event onto the bus, and not have it as part of the AR's event stream. I had though AR's having a reference to the event bus was starting to violate SRP, but maybe I'm wrong on that.
I've included simplified code to help my description. Anyone with some helpful tips? Thanks,Phil
public class Event : EventSourcedAggregateRoot<Guid>
{
#region Fields
private readonly HashSet<Attendee> _attendance = new HashSet<Attendee>();
private Guid _eventID;
private string _title;
#endregion
#region Constructors
[Obsolete]
private Event()
{
}
public Event(LocalDate date, string title)
{
HandleEvent(new EventCreated(date, title, new GuidCombGenerator().GenerateNewId()));
}
public Event(IEnumerable<IAggregateEvent<Guid>> #events)
{
LoadsFromHistory(#events);
}
#endregion
#region Properties and Indexers
public IReadOnlyCollection<Attendee> Attendance
{
get { return _attendance.ToArray(); }
}
public Guid EventID
{
get { return _eventID; }
private set
{
if (_eventID == new Guid()) _eventID = value;
else throw new FieldAccessException("Cannot change the ID of an entity.");
}
}
public LocalDate Date { get; private set; }
public override Guid ID
{
get { return EventID; }
set { EventID = value; }
}
public string Title
{
get { return _title; }
private set
{
Guard.That(() => value).IsNotNullOrWhiteSpace();
_title = value;
}
}
#endregion
#region Methods
public override void Delete()
{
if (!Deleted)
HandleEvent(new EventDeleted(EventID));
}
public void UpdateEvent(LocalDate date, string title)
{
HandleEvent(new EventUpdated(date, title, EventID));
}
public void AddAttendee(Guid memberID)
{
Guard.That(() => _attendance).IsTrue(set => set.All(attendee => attendee.MemberID != memberID), "Attendee already exists");
HandleEvent(new AttendeeAdded(memberID, EventID));
}
public void DeleteAttendee(Guid memberID)
{
Guard.That(() => memberID).IsTrue(x => Attendance.Any(a => attendee.MemberID == memberID), "MemberID does not exist");
HandleEvent(new AttendeeDeleted(memberID, EventID));
}
internal void RespondIsComing(Guid memberID)
{
Guard.That(() => memberID).IsTrue(x => Attendance.Any(a => attendee.MemberID == memberID), "MemberID does not exist");
HandleEvent(new AttendeeRespondedAsComing(memberID, EventID));
}
internal void RespondNotComing(Guid memberID)
{
Guard.That(() => memberID).IsTrue(x => Attendance.Any(a => attendee.MemberID == memberID), "MemberID does not exist");
HandleEvent(new AttendeeRespondedAsNotComing(memberID, EventID));
}
#endregion
#region Event Handlers
private void Apply(EventCreated #event)
{
Date = #event.Date;
Title = #event.Title;
EventID = #event.EventID;
}
private void Apply(EventDeleted #event)
{
Deleted = true;
}
private void Apply(AttendeeAdded #event)
{
_attendance.Add(new Attendee(#event.MemberID, #event.EventID));
}
private void Apply(EventUpdated #event)
{
Title = #event.Title;
Date = #event.Date;
}
private void Apply(AttendeeRespondedAsComing #event)
{
var attendee = GetAttendee(#event.AttendeeID);
attendee.Accept();
}
private void Apply(AttendeeRespondedAsNotComing #event)
{
var attendee = GetAttendee(#event.AttendeeID);
attendee.Reject();
}
private void Apply(AttendeeDeleted #event)
{
_attendance.RemoveWhere(x => x.AttendeeID == #event.AttendeeID);
}
protected override void ApplyEvent(IAggregateEvent #event)
{
Apply((dynamic) #event);
}
#endregion
}
public class Attendee
{
#region AttendenceResponse enum
public enum AttendenceResponse
{
TBD,
Coming,
NotComing
}
#endregion
#region Fields
private IAttendenceResponseState _attendState;
private readonly Guid _eventID;
private readonly Guid _memberID;
#endregion
#region Constructors
public Attendee(Guid memberID, Guid EventID)
{
_memberID = memberID;
_eventID = EventID;
_attendState = new TBD(this);
}
#endregion
#region Properties and Indexers
public IAttendenceResponseState AttendingState
{
get { return _attendState; }
private set { _attendState = value; }
}
public Guid EventID
{
get { return _eventID; }
}
public Guid MemberID
{
get { return _memberID; }
}
#endregion
#region Methods
public void Accept()
{
_attendState.Accept();
}
public void Reject()
{
_attendState.Reject();
}
#endregion
#region Nested type: IAttendenceResponseState
public interface IAttendenceResponseState
{
#region Properties and Indexers
AttendenceResponse AttendenceResponse { get; }
#endregion
#region Methods
void Accept();
void Reject();
#endregion
}
#endregion
#region Nested type: Coming
private class Coming : IAttendenceResponseState
{
#region Fields
private readonly Attendee _attendee;
#endregion
#region Constructors
public Coming(Attendee attendee)
{
_attendee = attendee;
}
#endregion
#region IAttendenceResponseState Members
public void Accept()
{
}
public AttendenceResponse AttendenceResponse
{
get { return AttendenceResponse.Coming; }
}
public void Reject()
{
_attendee.AttendingState = (new NotComing(_attendee));
//Here is where I would like to 'raise' an event
}
#endregion
}
#endregion
#region Nested type: NotComing
private class NotComing : IAttendenceResponseState
{
#region Fields
private readonly Attendee _attendee;
#endregion
#region Constructors
public NotComing(Attendee attendee)
{
_attendee = attendee;
}
#endregion
#region IAttendenceResponseState Members
public void Accept()
{
_attendee.AttendingState = (new Coming(_attendee));
//Here is where I would like to 'raise' an event
}
public AttendenceResponse AttendenceResponse
{
get { return AttendenceResponse.NotComing; }
}
public void Reject()
{
}
#endregion
}
#endregion
#region Nested type: TBD
private class TBD : IAttendenceResponseState
{
#region Fields
private readonly Attendee _attendee;
#endregion
#region Constructors
public TBD(Attendee attendee)
{
_attendee = attendee;
}
#endregion
#region IAttendenceResponseState Members
public void Accept()
{
_attendee.AttendingState = (new Coming(_attendee));
}
public AttendenceResponse AttendenceResponse
{
get { return AttendenceResponse.TBD; }
}
public void Reject()
{
_attendee.AttendingState = (new NotComing(_attendee));
}
#endregion
}
#endregion
}
Reply to mynkow's response:
I expose some of the state (read-only mind you) so that I might create projections of the current state of an aggregate. How would you normally do this? Do you create projection directly from events (this seems more complicated then reading the current state from the aggregate), or do you have your aggregate create DTOs?
I had public void AddAttendee(Guid memberID) before, but I switch it to Member to try force that a valid member would have to exist. I think I was wrong in doing this, and have since created an Attendance manager that does this validation and calls this method. (code updated to reflect this)
I used nested classes to try to signify that it was parent child relationship, but I agree, I don't much like how large it makes the Event class. The AttendenceResponseState is nested however so that it can modify the Attendee's private state. Do you think this use is valid? (code updated to move Attendee outside of Event class)
Just to be clear, AttendenceResponseState is a implementation of the State Pattern, not the Attendee's full state (conflicting words :))
And I agree that Attendee doesn't really need to be an entity, but the ID is from another system that I have to work with, so I thought I would use it here. Some stuff is lost in the preparation of the code for SO.
I personally don't like separating the aggregates state from the aggregate, but just as a matter of personal taste. I might review that choice if I have to implement momento's, or as I gain more experience :). Also are Ports the same as Sagas?
Can you talk more to how an aggregate would produce more then one event? I think this is one of the things I am trying to do. Is it ok to call an ApplyEvent, then perform more logic and possibly call ApplyEvent a second time?
Thanks for your input, and if you have any other notes I'll be glad to hear them.
I will address the things I do not like. This does not mean it is the right way of doing it.
Your aggregate has public properties exposing some kind of a state. I would remove them.
public void AddAttendee(Member member) IF member is another aggregate I would reference it with the aggregate ID instead of the Member type. public void AddAttendee(MemberId member)
It looks like too complicated implementation for me with all the nested classes.
The Aggregate should be responsible for validating the incoming data against the state (the state is another class with no logic, like DTO). The aggregate also creates and collects new events which are produced. When the operation is done the command handler persists all uncommitted events. If the operation is successful the events are published.
Remember, one command must update only one aggregate and invoke only one aggregate method, but updating the aggregate may produce 1 or more events.
Make all your projections idempotent.
Use Ports (these are event handlers which handle events from the current bounded context or other bounded context and produce commands for the current bounded context) to update multiple aggregates or handle events from other bounded contexts. Ports can only query the read model and produce commands, never update the read model from there.
I rarely use Entities in my model. Almost everything I design is accomplished by aggregates and value objects. Blame me :).
May be this answer does not meet your expectations. I just wanted to share some knowledge which works for me. I have 2 systems in production following these rules. Simple with less bugs. I will be glad if this info has any values to you or others reading this.
Happy coding
EDIT: Some code. Please read the comments. Also, I do not see any value using the Attendee classes. More info pls.
public class Event : EventSourcedAggregateRoot<Guid>
{
private readonly HashSet<AttendeeId> _attendance = new HashSet<Attendee>();
private EventId _eventID;
private string _title;
// generating AR ID should not be a responsibility of the AR
// All my IDs are generated by the client or the place where commands are created
// One thing about building CQRS systems is the you must trust the client. This is important. Google it.
public Event(EventId id, LocalDate date, string title, List<AttendeeId> attendees/* Can you create an event without attendees? */)
{
HandleEvent(new EventCreated(date, title, attendees, id));
}
This override reminds me of an active record pattern.
//public override void Delete()
public void Cancel()
{
if (!Deleted)
HandleEvent(new EventDeleted(EventID));
}
// May be you could split it to two events. The other one could be RescheduleEvent
// and all attendees will be notified. But changing the title could be just a typo.
public void UpdateEvent(LocalDate date, string title)
{
HandleEvent(new EventUpdated(date, title, EventID));
}
public void AddAttendee(AttendeeId memberID)
{
Guard.That(() => _attendance).IsTrue(set => set.All(attendee => attendee.MemberID != memberID), "Attendee already exists");
HandleEvent(new AttendeeAdded(memberID, EventID));
}
public void DeleteAttendee(AttendeeId memberID)
{
Guard.That(() => memberID).IsTrue(x => Attendance.Any(a => attendee.MemberID == memberID), "MemberID does not exist");
HandleEvent(new AttendeeDeleted(memberID, EventID));
}
internal void RespondIsComing(AttendeeId memberID)
{
Guard.That(() => memberID).IsTrue(x => Attendance.Any(a => attendee.MemberID == memberID), "MemberID does not exist");
HandleEvent(new AttendeeRespondedAsComing(memberID, EventID));
}
internal void RespondNotComing(AttendeeId memberID)
{
Guard.That(() => memberID).IsTrue(x => Attendance.Any(a => attendee.MemberID == memberID), "MemberID does not exist");
HandleEvent(new AttendeeRespondedAsNotComing(memberID, EventID));
}
private void Apply(EventCreated #event)
{
Date = #event.Date;
Title = #event.Title;
EventID = #event.EventID;
}
private void Apply(EventDeleted #event)
{
Deleted = true;
}
private void Apply(AttendeeAdded #event)
{
_attendance.Add(new Attendee(#event.MemberID, #event.EventID));
}
private void Apply(EventUpdated #event)
{
Title = #event.Title;
Date = #event.Date;
}
private void Apply(AttendeeRespondedAsComing #event)
{
var attendee = GetAttendee(#event.AttendeeID); // What this method does?
//attendee.Accept();
}
private void Apply(AttendeeRespondedAsNotComing #event)
{
var attendee = GetAttendee(#event.AttendeeID);// What this method does?
//attendee.Reject();
}
private void Apply(AttendeeDeleted #event)
{
_attendance.RemoveWhere(x => x.AttendeeID == #event.AttendeeID);
}
protected override void ApplyEvent(IAggregateEvent #event)
{
Apply((dynamic) #event);
}
}
Reply => Reply to mynkow's response:
1) I would copy all the information I need from the aggregate's state to the event and publish that event. The event handler which creates DTOs and stores them in database to serve UI is called projection. You may play with the words and call that DTO a projection. But the simple rule here is: NO INNER JOINS, NO SELECT FROM ANOTHER TABLE. You can save, select, update information only from one table.
2) Guid works for some time. Using the AR type is really bad. Create a value object which represents the AR ID.
3) It is valid as long as only the Aggregate root takes care about all the invariants including related entities.
State pattern => nice. I use the same => https://github.com/Elders/Cronus/tree/master/Cronus.Persistence.MSSQL/src/Elders.Cronus.Sample.IdentityAndAccess/Accounts
Entity vs ValueObject => Best example ever. I always use it when I teach the juniors => http://lostechies.com/joeocampo/2007/04/15/a-discussion-on-domain-driven-design-entities/
Imagine that a customer buys something from eCommerce website. He spends $100 each month. You can have a rule that if you have 10 conseq. months with purchases > $100 you attach a gift to the clients order. This is how you can have more than 1 event. And this is where the interesting stuff actually lives. ;)
Related
I have a new Xamarin Forms 5 app and I'm having trouble with data binding.
First, I display a message that tells the user how many items are in his list. Initially, this is 0. It's displayed by DisplayMessage property of the view model.
Then, the Init() method gets called and once the API call is finished, there are some items in MyList. I put break points to make sure that the API call works and I end up with some data in MyList property.
Because I change the value of message in my Init() method, I was expecting the message to change and display the number of items in the list but it's not changing even though I have some items in MyList.
I created a new ViewModel that looks like this:
public class MyViewModel : BaseViewModel
{
public List<MyItem> MyList { get; set; } = new List<MyItem>();
string message = "You have no items in your list... ";
public string DisplayMessage
{
get => message;
set
{
if(message == value)
return;
message = value;
OnPropertyChanged();
}
}
public async void Init()
{
var data = await _myService.GetData();
if(data.Count > 0)
message = $"You have {data.Count} items in your list!";
MyList = data;
}
}
My MainPage code behind looks like this:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage : ContentPage
{
MyViewModel _vm;
MainPage()
{
InitializeComponent();
_vm = new MyViewModel();
this.BindingContext = _vm;
}
protected override void OnAppearing()
{
base.OnAppearing();
_vm.Init();
}
}
I didn't change anyting in the base view model, except I added my service and it looks like this:
public class BaseViewModel : INotifyPropertyChanged
{
public IMyApiService MyApi => DependencyService.Get<IMyApiService>();
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I'd appreciatae someone telling me where my mistake is. Thanks.
Without seeing the Xaml, I can't 100% answer, but here are a couple of things I see:
You are setting the "message" through the field, not the property. Since you are setting the field directly the OnPropertyChanged event isn't firing so the UI isn't getting notified that the value has changed.
I am guessing you are binding "MyList" to some sort of CollectionView or something? If it's a readonly view, using a List is ok as the collection is never updated. However, if you plan on adding or removing items at runtime, it needs to be an "ObservableCollection" for the same reason as above, the UI isn't notified of new items in a List, but an ObservableCollection will notify the UI of changes to it, so it can update.
Is what Jason mentions above in his comment. The MyList property should be setup like the other properties with the OnPropertyChanged.
I'm changing the label in the class constructor and it works fine, the label is updated ("0"). I'm also trying to update the label when I click in a button, but it's not working ("X"). I noticed debugging that the label value is updated, PropertyChanged is triggered, but the view doesn't change.
public class HomeViewModel : ViewModelBase
{
string playerA;
public string PlayerA
{
get
{
return playerA;
}
set
{
playerA = value;
this.Notify("playerA");
}
}
public ICommand PlayerA_Plus_Command
{
get;
set;
}
public HomeViewModel()
{
this.PlayerA_Plus_Command = new Command(this.PlayerA_Plus);
this.PlayerA = "0";
}
public void PlayerA_Plus()
{
this.PlayerA = "X";
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
The name of the parameter passed in your PropertyChangedEventArgs is wrong. You are using "playerA" but the name of the (public) property is "PlayerA" (uppercase "P"). Change this.Notify("playerA"); to this.Notify("PlayerA"); or even better:
Notify(nameof(PlayerA));
You can completely get rid of passing the name of the param by adding a [CallerMemberName] attribute to the Notify() method.
protected void Notify([CallerMemberName] string propertyName = null)
This allows you to just call Notify() without parameters and the name of the changed property will automatically be used.
I'm trying to see how it would be possible to chain together x number of ObservableCollections.CollectionChanged event, exposed as a N level depth object tree to a single parent level CollectionChanged event that consumers can listen to? Essentially I want to funnel or bubble all child CollectionChanged events up to the top most parent. A number of solution I've noticed that tackle similar issues make an assumption of a fixed number of levels, say 2 deep. I idea is to support any level of depth.
Originally I had hoped I could just pass the instance of the FieldInfos to the child constructors and attach directly to the handler. However i get an error stating the "Event 'CollectionChanged' can only appear on the left hand side of+= or -=.
Thanks,
public class FieldInfos
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private ObservableCollection<Field> _fields;
public ObservableCollection<Field> Fields => _fields ?? (_fields = new ObservableCollection<Field>());
}
public class Field
{
public string Name;
private ObservableCollection<FieldInstance> _instances;
public ObservableCollection<FieldInstance> Instances => _instances ?? (_instances = new ObservableCollection<FieldInstance>());
}
public class FieldInstance
{
public string Id { get; set; }
}
The simplest approach is subclass the original ObservableCollection<T>.
You'd need at least one interface to avoid covariance problems. You can also have your own classes to implement the INotifyDescendantsChanged interface.
public interface INotifyDescendantsChanged
{
event NotifyCollectionChangedEventHandler DescendantsChanged;
}
public class ObservableBubbleCollection<T> : ObservableCollection<T>, INotifyDescendantsChanged
{
public event NotifyCollectionChangedEventHandler DescendantsChanged;
protected virtual void OnDescendantsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handler = DescendantsChanged;
if (handler != null)
handler(sender, e);
}
private readonly Func<T, INotifyDescendantsChanged> childSelector;
public ObservableBubbleCollection() { }
public ObservableBubbleCollection(Func<T, INotifyDescendantsChanged> childSelector)
{
this.childSelector = childSelector;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
OnDescendantsChanged(this, e);
if (childSelector == null)
return;
if (e.NewItems != null)
foreach (var item in e.NewItems.Cast<T>())
childSelector(item).DescendantsChanged += OnDescendantsChanged;
if (e.OldItems != null)
foreach (var item in e.OldItems.Cast<T>())
childSelector(item).DescendantsChanged -= OnDescendantsChanged;
}
}
To use it, replace instances of ObservableCollection and pass a selector to the collection.
public class FieldInfos
{
private ObservableBubbleCollection<Field> _fields;
public ObservableBubbleCollection<Field> Fields => _fields ?? (_fields = new ObservableBubbleCollection<Field>(fi => fi.Instances));
}
public class Field
{
public string Name;
private ObservableBubbleCollection<FieldInstance> _instances;
public ObservableBubbleCollection<FieldInstance> Instances => _instances ?? (_instances = new ObservableBubbleCollection<FieldInstance>());
}
public class FieldInstance
{
public string Id { get; set; }
}
static class Program
{
static void Main(string[] args)
{
var fi = new FieldInfos();
fi.Fields.DescendantsChanged += (sender, e) =>
{
Console.WriteLine("Change from {0}", sender.GetType());
};
var field = new Field();
fi.Fields.Add(field);
field.Instances.Add(new FieldInstance());
Console.ReadLine();
}
}
Trying to model a system sending out notifications from a number of publishers using RX.
I have two custom interfaces ITopicObservable and ITopicObserver to model the fact that the implementing classes will have other properties and methods apart from the IObservable and IObserver interfaces.
The problem I have is that my thinking is I should be able to add a number of observables together, merge them together and subscribe to an observer to provide updates from all merged observables. However the code with "the issue" comment throws an invalid cast exception.
The use case is a number of independent sensors each monitoring a temperature in a box for example that aggregate all their reports to one temperature report which is then subscribed to by a temperature health monitor.
What am I missing here? Or is there a better way to implement the scenario using RX?
Code below
using System;
using System.Reactive.Linq;
using System.Collections.Generic;
namespace test
{
class MainClass
{
public static void Main (string[] args)
{
Console.WriteLine ("Hello World!");
var to = new TopicObserver ();
var s = new TopicObservable ("test");
var agg = new AggregatedTopicObservable ();
agg.Add (s);
agg.Subscribe (to);
}
}
public interface ITopicObservable<TType>:IObservable<TType>
{
string Name{get;}
}
public class TopicObservable:ITopicObservable<int>
{
public TopicObservable(string name)
{
Name = name;
}
#region IObservable implementation
public IDisposable Subscribe (IObserver<int> observer)
{
return null;
}
#endregion
#region ITopicObservable implementation
public string Name { get;private set;}
#endregion
}
public class AggregatedTopicObservable:ITopicObservable<int>
{
List<TopicObservable> _topics;
private ITopicObservable<int> _observable;
private IDisposable _disposable;
public AggregatedTopicObservable()
{
_topics = new List<TopicObservable>();
}
public void Add(ITopicObservable<int> observable)
{
_topics.Add ((TopicObservable)observable);
}
#region IObservable implementation
public IDisposable Subscribe (IObserver<int> observer)
{
_observable = (ITopicObservable<int>)_topics.Merge ();
_disposable = _observable.Subscribe(observer);
return _disposable;
}
#endregion
#region ITopicObservable implementation
public string Name { get;private set;}
#endregion
}
public interface ITopicObserver<TType>:IObserver<TType>
{
string Name{get;}
}
public class TopicObserver:ITopicObserver<int>
{
#region IObserver implementation
public void OnNext (int value)
{
Console.WriteLine ("next {0}", value);
}
public void OnError (Exception error)
{
Console.WriteLine ("error {0}", error.Message);
}
public void OnCompleted ()
{
Console.WriteLine ("finished");
}
#endregion
#region ITopicObserver implementation
public string Name { get;private set;}
#endregion
}
}
My first thought, is that you shouldn't implement IObservable<T>, you should compose it by exposing it as a property or the result of a method.
Second thought is that there are operators in Rx that excel at merging/aggregating multiple sequences together.
You should favor using those.
Third, which is similar to the first, you generally don't implement IObserver<T>, you just subscribe to the observable sequence and provide delegates for each call back (OnNext, OnError and OnComplete)
So your code basically is reduced to
Console.WriteLine("Hello World!");
var topic1 = TopicListener("test1");
var topic2 = TopicListener("test2");
topic1.Merge(topic2)
.Subscribe(
val => { Console.WriteLine("One of the topics published this value {0}", val);},
ex => { Console.WriteLine("One of the topics errored. Now the whole sequence is dead {0}", ex);},
() => {Console.WriteLine("All topics have completed.");});
Where TopicListener(string) is just a method that returns IObservable<T>.
The implementation of the TopicListener(string) method would most probably use Observable.Create.
It may help to see examples of mapping Rx over a Topic based messaging system.
There is an example of how you can layer Rx over TibRv topics here https://github.com/LeeCampbell/RxCookbook/blob/master/IO/Comms/TibRvSample.linq
The signature of the .Merge(...) operator that you're using is:
IObservable<TSource> Merge<TSource>(this IEnumerable<IObservable<TSource>> sources)
The actual type returned by this .Merge() is:
System.Reactive.Linq.ObservableImpl.Merge`1[System.Int32]
...so it should be fairly clear that calling (ITopicObservable<int>)_topics.Merge(); would fail.
Lee's advice not to implement either of IObservable<> or IObserver<> is the correct one. It leads to errors like the one above.
If you had to do something like this, I would do it this way:
public interface ITopic
{
string Name { get; }
}
public interface ITopicObservable<TType> : ITopic, IObservable<TType>
{ }
public interface ITopicSubject<TType> : ISubject<TType>, ITopicObservable<TType>
{ }
public interface ITopicObserver<TType> : ITopic, IObserver<TType>
{ }
public class Topic
{
public string Name { get; private set; }
public Topic(string name)
{
this.Name = name;
}
}
public class TopicSubject : Topic, ITopicSubject<int>
{
private Subject<int> _subject = new Subject<int>();
public TopicSubject(string name)
: base(name)
{ }
public IDisposable Subscribe(IObserver<int> observer)
{
return _subject.Subscribe(observer);
}
public void OnNext(int value)
{
_subject.OnNext(value);
}
public void OnError(Exception error)
{
_subject.OnError(error);
}
public void OnCompleted()
{
_subject.OnCompleted();
}
}
public class AggregatedTopicObservable : Topic, ITopicObservable<int>
{
List<ITopicObservable<int>> _topics = new List<ITopicObservable<int>>();
public AggregatedTopicObservable(string name)
: base(name)
{ }
public void Add(ITopicObservable<int> observable)
{
_topics.Add(observable);
}
public IDisposable Subscribe(IObserver<int> observer)
{
return _topics.Merge().Subscribe(observer);
}
}
public class TopicObserver : Topic, ITopicObserver<int>
{
private IObserver<int> _observer;
public TopicObserver(string name)
: base(name)
{
_observer =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
}
public void OnNext(int value)
{
_observer.OnNext(value);
}
public void OnError(Exception error)
{
_observer.OnError(error);
}
public void OnCompleted()
{
_observer.OnCompleted();
}
}
And run it with:
var to = new TopicObserver("watching");
var ts1 = new TopicSubject("topic 1");
var ts2 = new TopicSubject("topic 2");
var agg = new AggregatedTopicObservable("agg");
agg.Add(ts1);
agg.Add(ts2);
agg.Subscribe(to);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
Which gives:
next 42
next 1
finished
But apart from being able to give everything a name (which I'm not sure how it helps) you could always do this:
var to =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
var ts1 = new Subject<int>();
var ts2 = new Subject<int>();
var agg = new [] { ts1, ts2 }.Merge();
agg.Subscribe(to);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
Same output with no interfaces and classes.
There's even a more interesting way. Try this:
var to =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
var agg = new Subject<IObservable<int>>();
agg.Merge().Subscribe(to);
var ts1 = new Subject<int>();
var ts2 = new Subject<int>();
agg.OnNext(ts1);
agg.OnNext(ts2);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
var ts3 = new Subject<int>();
agg.OnNext(ts3);
ts3.OnNext(99);
ts3.OnCompleted();
This produces:
next 42
next 1
next 99
It allows you to add new source observables after the merge!
I have a method where the user can search for a article number and if its available in the database the articlenumber gets bound to a BindingList. Now I want to let the user know if the article is not available in database. How do I do that the right way?
Just pass the message errorMessage to my interface method?
Presenter:
string errorMessage;
_view.ErrorMessage(errorMessage);
View:
public void ErrorMessage(string errorMessage)
{
MessageBox.Show(errorMessage);
}
Would you do it the same way?
We bubble an event. In the presenter you register that event:
public event PresenterEventHandler Message;
And then raise it like so:
PresenterEventArgs pe = new PresenterEventArgs("Error message", Status.Error);
this.Message(this, pe);
Then in the view:
protected override void OnInit(EventArgs e)
{
this.presenter = new MyPresenter(this, MyBusinessService.Instance);
this.presenter.Message += new PresenterEventHandler(presenter_Message);
}
void presenter_Message(object sender, PresenterEventArgs pe)
{
// display error message
}
You can pass different types of statuses back to the view in this way, and not just error messages. We have Success, Error, Locked, Warning, Help.
In the case of error messages I would call some base functionality. This way you could choose wether to update the status window on the bottom left and/or display a modal message box.
In the presenter:
_windowManager.NoItemFound(errorMessage)
In the window manager:
_statusBox.Text = errorMessage; MessageBox.Show(errorMessage);
We should not re-invent the wheel ....
You should simply throw an exception in your model.
Then, the view will catch the exception using a try catch block.
In the "catch", show your message box.
If its MVP - Passive View, then the View interface should have a property that could read:
public interface IArticleListView {
// more stuff here...
bool ErrorMessageVisible { set; }
string ErrorMessage { set; }
// more stuff here...
int ArticleNumber { get; }
}
public interface IPresenter {
public void Update();
}
public class ArticleListPresenter : IPresenter {
IViewArticleList _view;
public ArticleListPresenter(IArticleListView view) {
this._view = view;
// you could update the view here or with an update method,
// completely up to you.
}
// Assuming you are using a fine grained presenter
public void HandleArticleNumberSearch() {
bool articleNotFound;
// search for the article ...
if ( articleNotFound ) {
this._view.ErrorMessageVisible = true;
this._view.ErrorMessage = string.Format("The article #{0} was not found.", number);
}
}
}
public class ArticleList : Page, IArticleListView {
ArticleListPresenter _presenter;
public bool ErrorMessageVisible {
set { this.lblErrorMessage.Visible = value; }
}
public bool ErrorMessage {
set { this.lblErrorMessage.Text = value; }
}
public int ArticleNumber {
// You have to do some sort of validation here, but I'll keep it simple
get { return Integer.Parse(this.txtArticleNumber.Text); }
}
protected override void OnInit(EventArgs e) {
this._presenter = new ArticleListPresenter(this);
}
protected void Page_Load(object sender, EventArgs e) {
// this implementation keeps the state in the View, and updates it
// in the presenter: Passive View
if(!this.IsPostBack) {
this._presenter.Update();
}
}
protected void OnSearchArticleButtonClick(object sender, EventArgs e) {
this._presenter.HandleArticleNumberSearch();
}
}
That's what I do.
Another way I've read about would be for the model to know how to show an error (perhaps by an ErrorMessagePresenter) so the error is detached from the original presenter.
I haven't really found a use for that, for me, it always ends in the presenter and view implementing both interfaces.