How to handle domain events that are raised by event handlers? - c#

I've implemented the following pattern by jbogard:
http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/
Imagine the following entity Coupon and event CouponActivatedEvent:
public class Coupon : DomainEntity
{
public virtual User User { get; private set; }
// ...omitted...
public void Activate(User user)
{
if (User != null)
throw new InvalidOperationException("Coupon already activated");
User = user;
Events.Add(new CouponActivatedEvent(this));
}
}
The following event handler CouponActivatedHandler:
public class CouponActivatedHandler : IDomainEventHandler<CouponActivatedEvent>
{
public void Handle(CouponActivatedEvent e)
{
// user gets 5 credits because coupon was activated
for (int i = 0; i < 5; i++)
{
e.Coupon.User.AddCredit(); // raises UserReceivedCreditEvent and CreditCreatedEvent
}
}
}
The following SaveChanges override on DbContext (Entity Framework 6), taken from jbogard's blog post:
public override int SaveChanges()
{
var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
.Select(po => po.Entity)
.Where(po => po.Events.Any())
.ToArray();
foreach (var entity in domainEventEntities)
{
var events = entity.Events.ToArray();
entity.Events.Clear();
foreach (var domainEvent in events)
{
_dispatcher.Dispatch(domainEvent);
}
}
return base.SaveChanges();
}
If we now activate a coupon, this will raise the CouponActivatedEvent. When calling SaveChanges, the handler will be executed, and UserReceivedCreditEvent and CreditCreatedEvent will be raised. They won't be handled though. Am I misunderstanding the pattern? Or is the SaveChanges override not appropriate?
I've considered creating a loop that will repeat until no new events are raised before moving on to base.SaveChanges();... but I'm worried that I will create endless loops accidentally. Like so:
public override int SaveChanges()
{
do
{
var domainEventEntities = ChangeTracker.Entries<IDomainEntity>()
.Select(po => po.Entity)
.Where(po => po.Events.Any())
.ToArray();
foreach (var entity in domainEventEntities)
{
var events = entity.Events.ToArray();
entity.Events.Clear();
foreach (var domainEvent in events)
{
_dispatcher.Dispatch(domainEvent);
}
}
}
while (ChangeTracker.Entries<IDomainEntity>().Any(po => po.Entity.Events.Any()));
return base.SaveChanges();
}

Yeah, you misunderstood things. A Domain Event is not like a C# event, it's a message about what changed in the Domain. One rule is that an event is something that happened, it's in the past. Thus, an event handler simply can't (it shouldn't) change the event, it's like changing the past.
You CouponActivatedHandler should at least get the User entity form a repository then update it with the number of credits, then save it, then publish an UserCreditsAdded event. Even better, the handler should just create and send a command AddCreditsToUser .
With Domain Events pattern, an operation is simply a chain of command->event-> command-> event etc. The event handler is usually a service (or a method in one) which takes care only of that bit. The sender of the event won't know anything about the handler and vice-versa.
As a thumb rule, a Domain object generates an event when its state has changed. A service will take those events then send them to a service bus (for a simple app, a DI Container is enough) which will publish them to be handled by anyone interested (this means services from the local app or other apps subscribed to that bus).
Never forget that Domain Events is a high level pattern used when doing the architecture of an app, it's not just another way to do object events (like C#'s events).

The linked implementation of domain events does not handle potentially nested events raised by event handlers. If you read through the authors comments, he himself once mentioned that he "tries to avoid it".
IMO, what you implemented with the loop makes sense. But if you are not certain about getting into an indefinite loop, I suggest you implement a circuit breaker pattern there.

Related

Is it possible to have a Business Event call Custom Code in Acumatica?

When my user adds an appointment, he wants to have 2 things happen:
Send an acknowledgement of the appointment to the customer's cell phone, with information about the appointment
24 hours before the appointment, to have a reminder sent out
The main issue is that the event screen displays dates as UTC. This, of course, confuses the customer. So, I need to change the date and format the text message via code.
When I first did this, there was no way to call a custom method from a business event -- so I actually send the message up to Twilio, catch that with a Webhook that sends it into my custom DLL. That massages the message, making everything look right, and then sends it back through Twilio to the customer.
But this is costly (2 messages sent and 1 received for every event) and needlessly complicated. I want to simplify it now, because I have been told there is added functionality in Business events now that allows a call out into custom code. Is this true?
I was told that this would be available starting in 2020 R2. I am looking for it in the docs and training classes, but I can't see anywhere that this is possible.
How do I call custom code from a business event? Can I set up a subscriber that is in a custom DLL?
Is there something that describes this process somewhere? Or did this never make it into the product?
If you're looking to implement a custom Business Event subscriber (coded in your own dll), I know that is possible in 2021 R1.
There are basic instructions in the Release Notes for Developers starting on page 15.
The upshot is you need to reference the PX.BusinessProcess.dll and implement the PX.BusinessProcess.Subscribers.ActionHandlers.IEventAction interface and either of the PX.BusinessProcess.Subscribers.Factories.IBPSubscriberActionHandlerFactory or PX.BusinessProcess.Subscribers.Factories.IBPSubscriberActionHandlerFactoryWithCreateAction interfaces.
Here is an example blog from crestwood to use business events to create an import scenario: https://www.crestwood.com/2020/05/19/using-business-events-to-create-transactions-employee-birthday-checks/
The gist of it would be to create the generic inquiry to monitor. Next, you would create a business event that ties to the generic inquiry. Go under subscribers, select Create New Subscriber, and then name it. It will load the import scenario, and attach the provider to the event.
For provider object in the business event, you can fill from Results or Previous Results.
This is based on the aborted shipments in sales demo, but it shows my custom action after matching shipment number.
once saved, you can see your business event shows up as a subscriber:
Just for the record, the code I was looking for I was in the Acumatica Help files, as TTook suggested. The reason I am publishing more on this is that the answer in help files aren't very reliable (sometimes) down the road, and I wanted a complete version of the code here -- including all of the using/includes. So, here it is:
This creates an event and a subscriber to respond to a Business Event. The code shows writing the screen data to a text file.
using System;
using System.Collections.Generic;
using System.Linq;
using PX.BusinessProcess.Subscribers.ActionHandlers;
using PX.BusinessProcess.Subscribers.Factories;
using PX.BusinessProcess.Event;
using PX.BusinessProcess.DAC;
using PX.BusinessProcess.UI;
using System.Threading;
using PX.Data;
using PX.Common;
using PX.SM;
using System.IO;
using PX.Data.Wiki.Parser;
using PX.PushNotifications;
namespace CustomSubscriber
{
//The custom subscriber that the system executes once the business event
//has occurred
public class CustomSMSEventAction : IEventAction
{
//The GUID that identifies a subscriber
public Guid Id { get; set; }
//The name of the subscriber of the custom type
public string Name { get; protected set; }
//The notification template
private readonly Notification _notificationTemplate;
//The method that writes the body of the notification to a text file
//once the business event has occurred
public void Process(MatchedRow[] eventRows, CancellationToken cancellation)
{
using (StreamWriter file =
new StreamWriter(#"C:\tmp\EventRows.txt"))
{
var graph = PXGenericInqGrph.CreateInstance(
_notificationTemplate.ScreenID);
var parameters = #eventRows.Select(
r => Tuple.Create<IDictionary<string, object>,
IDictionary<string, object>>(
r.NewRow?.ToDictionary(c => c.Key.FieldName, c => c.Value),
r.OldRow?.ToDictionary(c => c.Key.FieldName,
c => (c.Value as ValueWithInternal)?.ExternalValue ??
c.Value))).ToArray();
var body = PXTemplateContentParser.ScriptInstance.Process(
_notificationTemplate.Body, parameters, graph, null);
file.WriteLine(body);
}
}
//The CustomEventAction constructor
public CustomSMSEventAction(Guid id, Notification notification)
{
Id = id;
Name = notification.Name;
_notificationTemplate = notification;
}
}
//The class that creates and executes the custom subscriber
class CustomSubscriberHandlerFactory :
IBPSubscriberActionHandlerFactoryWithCreateAction
{
//The method that creates a subscriber with the specified ID
public IEventAction CreateActionHandler(Guid handlerId,
bool stopOnError, IEventDefinitionsProvider eventDefinitionsProvider)
{
var graph = PXGraph.CreateInstance<PXGraph>();
Notification notification = PXSelect<Notification,
Where<Notification.noteID,
Equal<Required<Notification.noteID>>>>
.Select(graph, handlerId).AsEnumerable().SingleOrDefault();
return new CustomSMSEventAction(handlerId, notification);
}
//The method that retrieves the list of subscribers of the custom type
public IEnumerable<BPHandler> GetHandlers(PXGraph graph)
{
return PXSelect<Notification, Where<Notification.screenID,
Equal<Current<BPEvent.screenID>>,
Or<Current<BPEvent.screenID>, IsNull>>>
.Select(graph).FirstTableItems.Where(c => c != null)
.Select(c => new BPHandler
{
Id = c.NoteID,
Name = c.Name,
Type = LocalizableMessages.CustomNotification
});
}
//The method that performs redirection to the subscriber
public void RedirectToHandler(Guid? handlerId)
{
var notificationMaint =
PXGraph.CreateInstance<SMNotificationMaint>();
notificationMaint.Message.Current =
notificationMaint.Notifications.
Search<Notification.noteID>(handlerId);
PXRedirectHelper.TryRedirect(notificationMaint,
PXRedirectHelper.WindowMode.New);
}
//A string identifier of the subscriber type that is
//exactly four characters long
public string Type
{
get { return "CTTP"; }
}
//A string label of the subscriber type
public string TypeName
{
get { return LocalizableMessages.CustomNotification; }
}
//A string identifier of the action that creates
//a subscriber of the custom type
public string CreateActionName
{
get { return "NewCustomNotification"; }
}
//A string label of the button that creates
//a subscriber of the custom type
public string CreateActionLabel
{
get { return LocalizableMessages.CreateCustomNotification; }
}
//The delegate for the action that creates
//a subscriber of the custom type
public Tuple<PXButtonDelegate, PXEventSubscriberAttribute[]>
getCreateActionDelegate(BusinessProcessEventMaint maintGraph)
{
PXButtonDelegate handler = (PXAdapter adapter) =>
{
if (maintGraph.Events?.Current?.ScreenID == null)
return adapter.Get();
var graph = PXGraph.CreateInstance<SMNotificationMaint>();
var cache = graph.Caches<Notification>();
var notification = (Notification)cache.CreateInstance();
var row = cache.InitNewRow(notification);
row.ScreenID = maintGraph.Events.Current.ScreenID;
cache.Insert(row);
var subscriber = new BPEventSubscriber();
var subscriberRow =
maintGraph.Subscribers.Cache.InitNewRow(subscriber);
subscriberRow.Type = Type;
subscriberRow.HandlerID = row.NoteID;
graph.Caches[typeof(BPEventSubscriber)].Insert(subscriberRow);
PXRedirectHelper.TryRedirect(graph,
PXRedirectHelper.WindowMode.NewWindow);
return adapter.Get();
};
return Tuple.Create(handler,
new PXEventSubscriberAttribute[]
{new PXButtonAttribute {
OnClosingPopup = PXSpecialButtonType.Refresh}});
}
}
//Localizable messages
[PXLocalizable]
public static class LocalizableMessages
{
public const string CustomNotification = "Custom SMS Notification";
public const string CreateCustomNotification = "Custom SMS Notification";
}
}

Encapsulating a class to run as a continual process with both internal and external events

I have an OPC Client that I am creating.
I am trying to encapsulate the Opc.Da.Server inside this Client with the required configuration parameters and subscriptions into a single class.
There are internal events that need to fire with in this class. I have two other custom events I created with in this class and I need to raise depending on what happens internally within the class - so my instantiating code can subscribe.
If I don't encapsulate the Opc.Da.Server ; the Subscription DataChange event gets raised and all is cool - but I want to encapsulate so I can make things easy on myself (doesn't seem so easy right now.) None of my events fire - not the encapsulated ones and therefore also not the external one.
If I am going about this all wrong or you see a better way speak freely I think I' am still teachable - Anyway my code is :
public class CLIENT
{
Server server {get; set;}
Subscription subscription {get; set;}
public CLIENT() { }
public void StartClient(Connection connection, string[] parameters)
{
server = new Server(connection , parameters);
subscription = CreateSubscriptions();
subscription.OnDataChange += subscription_DataChange();
}
// Event is not firing
void subscription_DataChange()
{
... do stuff something worth noting is found
OnSomethingSpecificHappened();
}
public event EventArgs<Something> OnSomethingSpecificHappened;
}
.. Windows Form
Form myApp
{
Connection connection {get; set;}
string[] parameters {get; set;}
CLIENT client {get; set;}
Form() {}
StartNewCLIENTe()
{
client = new CLIENT();
client.OnSomethingSpecificHappened += client_OnSomethingSpecificHappened();
client.StartClient(connection , parameters);
}
}
EDIT 5-27-2016
I now have managed to get the events to fire once and only once.
They are supposed to be continually updating .. it is as if my object is run and then gets disposed of. and I do not know why ..

Reactive Extensions in C#.NET throwing a 'System.Security.VerificationException' randomly

The exception message is not very helpful but here it is
'observableTeamMember.AssignedTaskEvents.Added' threw an exception of
type 'System.Security.VerificationException'
Method System.Reactive.Linq.Observable.FromEventPattern: type argument
'Bourne.iClean.Planning.Observables.NotifyChildAddedEventArgs`1[T]'
violates the constraint of type parameter 'TEventArgs'.
To give you a brief overview, We have a C#.NET windows service hosted server side which deals with requests from a web application. The service queries a 'planning model' which is a cached version of all the data objects, we maintain this cache in order to speed up responses to queries from the front end.
We are using reactive extensions to keep our data cache up to date whenever certain events occur (like updates to data objects). Some objects in the data cache have child events subscription and when any of these child objects are changed, we update the cache, for example- we have a TeamMemberObject as follows which has some child events associated with it
public interface IObservableTeamMember : IObservableEntity<TeamMember>
{
ChildEvents<TeamCalendar> TeamCalendarEvents {get;}
ChildEvents<AssignedTaskTeamMember> AssignedTaskEvents { get; }
ChildEvents<WorkplaceAssignedTaskTeamMember> WorkplaceAssignedTaskEvents { get; }
}
Whenever any of these child events/objects are updated, we update our cache using the following code. The block below in where I get the security exception
observableTeamMember.AssignedTaskEvents.Added.Subscribe((NotifyChildAddedEventArgs<AssignedTaskTeamMember> e) =>
{
this._addAssignedTaskEntry(e.AddedChild);
});
I am also including partial code for the Child Event Class below
public class ChildEvents<T> : IChildEvents<T>
{
public void Add(T child)
{
var args = new NotifyChildAddedEventArgs<T>(child);
_raiseChildAdded(args);
}
public void Remove(T child)
{
var args = new NotifyChildDeletedEventArgs<T>(child);
_raiseChildDeleted(args);
}
event NotifyChildAddedEventHandler<T> _baseTAdded;
protected void _raiseChildAdded(NotifyChildAddedEventArgs<T> args)
{
var childAdded = _baseTAdded;
if (childAdded != null)
childAdded(this, args);
}
private IObservable<NotifyChildAddedEventArgs<T>> _childAdded;
public IObservable<NotifyChildAddedEventArgs<T>> Added
{
get
{
if (_childAdded == null)
_childAdded = Observable.FromEventPattern<NotifyChildAddedEventHandler<T>, NotifyChildAddedEventArgs<T>>(
(handler) => _baseTAdded += handler,
(handler) => _baseTAdded -= handler)
.Select(e => e.EventArgs);
return _childAdded;
}
}
}
This exception only happens occasionally and we are a team of 3 and it has happened randomly to all of us at some point. We use reactive extensions throughout this project and we are unable to explain the cause. Any help is greatly appreciated.
Thanks
Tapashya
That error suggests that your Bourne.iClean.Planning.Observables.NotifyChildAddedEventArgs<T> does not inherit from System.EventArgs, which is required by Observable.FromEventPattern since it specifically expects you to confirm to the standard .NET event pattern.

Subscribing To ThrownExceptions in ReactiveUi

I am migrating to version 6 of Reactive UI, and am trying to more completely use the tools it provides, namely ThrownExceptions. Nothing happens when I subscribe to the thrown exceptions property. I'm sure I'm missing something just not sure what it is right now.
In my simplified example, there is a button with a command bound it it.
public ReactiveCommand<object> Delete { get; private set; }
public MainWindowViewModel()
{
Delete = ReactiveCommand.Create();
Delete.Subscribe(e => CommandExec());
Delete.ThrownExceptions.Subscribe(ex => HandleException(ex));
}
private object HandleException(Exception ex)
{
MessageBox.Show("Exception Handled");
return null;
}
public IObservable<object> CommandExec()
{
throw new Exception("throwing");
}
My assumption is that I would see an "Exception Handled" MessageBox when the exception was thrown. I'm sure i'm subscribing to something, it's just not clear what it is right now.
ThrownExceptions only applies to the background operation declared with CreateAsyncXYZ:
var someCmd = ReactiveCommand.CreateAsyncObservable(_ =>
Observable.Throw<Unit>(new Exception("Oh Noes!"));
someCmd.ThrownExceptions.Subscribe(ex => Console.WriteLine(ex.Message));
await someCmd.ExecuteAsync();
>>> Oh Noes!
In ReactiveUI, you should never put Interesting™ code inside the Subscribe block - Subscribe is solely to log the result of operations, or to wire up properties to other properties.

MVVM Implementation for WinForm application

I am trying to implement MVVM (Model View ViewModel) pattern for my WinForms application. I am using C# 2005.
My application has a MainForm (View) with 2 multi line textboxes and 3 buttons. The purpose of the 1st textbox is to show a running commentary of what the application is doing, when the button is clicked. I keep on appending lines to the TextBox to update the user what is happening. The purpose of the 2nd textbox is to update the user about any error condition, conflicts, duplicate values; in short, anything which is required by the user to review. It classifies each message as either an INFO or a WARNING or an ERROR. Each of the 3 buttons perform an action, and keeps updating the 2 textboxes.
I have created a MainFormViewModel class.
1st question:
When the user clicks on the button in MainForm, I have to clear the contents of the 2 textboxes, and disable the button so that it cant be clicked again until 1st operation is completed. Should I do this textbox and button updation directly in the MainForm or I should use MainFormViewModel in some way?
2nd question:
The button click calls a method on the MainFormViewModel class. Before calling the method and after calling the method, I want to show a message in the 1st textbox something like "Operation A started / ended". I do this by calling a Common class which has a Log method to log messages to a TextBox or a file or both. Again whether it is ok to do this directly from the MainForm? I call this logging method at the start and end of the event handler.
3rd question:
How do I propagate error messages from ViewModel back to View? I have created a custom Exception class "TbtException". So do I have to write 2 catch blocks in each and every button, one for TbtException and other for genetic Exception class?
Thanks.
You should perform operations in the view only with regard to the state of the ViewModel object. E.g. you shouldn't assume the view model is calculating when you click a button, but you should add a state to the view model that says it's doing something longer and then recognize that state in the view. You shouldn't disable or enable buttons in the view as you please, but only if there's a state that demands these buttons to be changed. This can go as far as to have a property that indicates which item in a list is currently selected, so the UI doesn't call the list control's SelectedItem member, but the viewmodel's. And when the user clicks remove, then the view model will remove the selected member from its list and the view is automatically updated through state changes in the form of events.
Here's what I would call a view model for your view. It exposes messages through an observable collection to which the view can bind (ie. register event handlers, since binding is not well supported in WinForms). The textbox at any time renders only the contents of the collection. It has actions for clearing those collections which your view can call. The view can also call actions of the underlying model, but it should be updated through the viewmodel only! The view should never register any event handlers for events exposed by the underlying model. If you ever want to do that you should hook up that event in the view model and expose it to the view there. Sometimes that may feel like "just another level of indirection" which is why it may be overkill for very simple applications such as yours.
public class MainFormViewModel : INotifyPropertyChanged {
private object syncObject = new object();
private MainFormModel model;
public virtual MainFormModel Model {
get { return model; }
set {
bool changed = (model != value);
if (changed && model != null) DeregisterModelEvents();
model = value;
if (changed) {
OnPropertyChanged("Model");
if (model != null) RegisterModelEvents();
}
}
}
private bool isCalculating;
public bool IsCalculating {
get { return isCalculating; }
protected set {
bool changed = (isCalculating != value);
isCalculating = value;
if (changed) OnPropertyChanged("IsCalculating");
}
}
public ObservableCollection<string> Messages { get; private set; }
public ObservableCollection<Exception> Exceptions { get; private set; }
protected MainFormViewModel() {
this.Messages = new ObservableCollection<string>();
this.Exceptions = new ObservableCollection<string>();
}
public MainFormViewModel(MainFormModel model)
: this() {
Model = model;
}
protected virtual void RegisterModelEvents() {
Model.NewMessage += new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown += new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void DeregisterModelEvents() {
Model.NewMessage -= new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown -= new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void Model_NewMessage(object sender, SomeEventArg e) {
Messages.Add(e.Message);
}
protected virtual void Model_ExceptionThrown(object sender, OtherEventArg e) {
Exceptions.Add(e.Exception);
}
public virtual void ClearMessages() {
lock (syncObject) {
IsCalculating = true;
try {
Messages.Clear();
} finally { IsCalculating = false; }
}
}
public virtual void ClearExceptions() {
lock (syncObject) {
IsCalculating = true;
try {
Exceptions.Clear();
} finally { IsCalculating = false; }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropetyChanged(string property) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
EDIT: Regarding exception handling
I would rather catch exceptions in the ViewModel than in the view. The view model is better suited to prepare them for display. I don't know how that works in WPF though. I've yet to program an application in WPF, we're doing a lot of WinForms still.
Opinions may vary, but I think generic try/catch clauses aren't really exception handling. I think you should rather test your UI very well and include exception handling only when necessary. Which is why you unit test your view model and user test the view. However if you really stick to the principle and avoid logic in the view, you can do a lot with unit tests.

Categories