dibley1973 / OpenRMS

Open RMS is an Open Source project with the intention of delivering a retail management platform that is free to install, use, modify and distribute.
GNU General Public License v3.0
9 stars 7 forks source link

Events/Notifications #19

Closed johncollinson2001 closed 8 years ago

johncollinson2001 commented 8 years ago

I think it's a given that contexts will need to know when things have happened in other contexts. E.g. a purchase order module may need to know when a product has been deleted or deactivated, so it can check if there are any outstanding purchase orders containing that product.

I think a lightweight event/notification type system would be good for now - is that the consensus view? Something along the lines of the domain events.

Duane raised a valid point on the phone, what happens if (using the example above) the product gets deleted, but the PO modification fails and the system if left in an inconsistent state? Can we manage events in some kind of transaction?

jadjare commented 8 years ago

I don't think it'll be wise to manage events in some kind of transaction. That implies an overall aggregate root that must maintain a valid state. If this is the case it implies they would live in the same bounded context.

When it comes to events it's never going to be possible to control who consumes them and whether the consuming context processes them successfully.

As such I think we need to carefully design the system to be able to handle such situations. For example, in the above example around deleting a product, one option would be:

Of course to avoid the unnecessary round tripping of events the ProductManagement service can first check the PO Management service for the presence of an order prior to requesting the delete, thus reducing the likelihood of the above events triggering.

dibley1973 commented 8 years ago

Interesting. ... That approach dos leave coupling to a minimum. I had not thought about that kind of approach. I guess that does give us the best of both worlds, the decoupled event based notifications and the ability forth product to be undeleted.

How long should the ProductManagement module "wait" with the product in the "deleted" state before it fully confirms the deletion and really deletes the product?

jadjare commented 8 years ago

In the above example, as far as the user is concerned, the product is deleted immediately. In reality it's just marked as deleted. It could either stay in that state for all time or perhaps a purging module would later completely purge the product from the system - say after X months of the last seen sale and zero stock. This of course is just a fictitious example, not saying it's the right solution for OpenRMS, that's need some thought.

In essence I guess I'm just describing a workflow.

I'm definitely convinced that events should not directly enforce transactional consistency. An event should be thought of as no different than a letter in the post and just like a real letter there's no guarantee of how long it will take to get there, whether a response will be received or if in deed it will ever be deleted.

If some sort of consistency is required across boundaries a workflow (of events) will need to be designed. But the handler of the originating event must be able to deal with the circumstance that it never gets a response, most likely either by never fully completing the action or by proceeding to complete after a period of time.

Don't forget that in most circumstances it should be possible to call a service method in another context prior to completing a command that has a dependency, thus greatly reducing the likelihood to need any sort of complex workflow management via events.

johncollinson2001 commented 8 years ago

I guess data consistency like described is often left to the database, but feels like there should be a solution in the domain so it's not dependent on the database.

If everything was one big single context, then typically the checks would happen in the app service prior to deletion of the product.

Rather than domain events for this type of consistency, how's about an interface in the context that is performing the command, which other contexts can implement, allowing the main context to evaluate if the command can be executed?

Something like the specification pattern would be good for this: https://en.m.wikipedia.org/wiki/Specification_pattern

johncollinson2001 commented 8 years ago

I think domain events should be left to things like firing off an email when an order is placed, or triggering some process to kick off when stock has been booked onto the system.

dibley1973 commented 8 years ago

Oooh. I like the look of the specification pattern. I must check that out further on the Pluralsight design patterns library course.

Would another option with the events be to raise a xChanging event, with an option to cancel first, so subscribers can check if something is lilkely to be able performed and set cancel = true if not, and then is all is good and the entity can and does change then an xChanged event is fired after which subscribers can then perform their actions, as they were all pre-checked. If anything goes wrong now then it really must be an exceptional circumstance and an exception is thrown. - MAybe a bit of an overkill?

jadjare commented 8 years ago

Only skim read the Specification Pattern but I'm not sure it's related to handling events. It sounds like it's a way of handling rules sets rather than just scripting out the logic in a set of specifically named methods. It's also criticised for being overly complex. I expect it only comes into play for very specific needs. Thus agree with John's comment around keeping things simple.

I assume this conversation is just theoretical? As we can figure out the right implementation when the need for events to communicate is required - probably when we have a second bounded context.

However, carrying on with the theory, as it's a interesting discussion nonetheless.... There may be two types of event we're talking about here! A domain event despatched when an aggregate is committed... As an aggregate should always be in a valid state, events despatched after a commit must not undermine that state. Thus they will always be expressed in the past tense, e.g. Changed. As it's generally desirable for an aggregate to never enter an invalid state I don't think it's wise to despatch Changing type events as that implies the aggregate maybe in an invalid state until all handlers have OK it. Problem is there's no way of knowing how many handlers there are, so by it's very design means that an aggregate is in a permanent invalid state. One exception to this, is arguably a second type of event, one that occurs within an aggregate root and only serves the purpose of separation of concerns.

This is all very theoretical of course and doesn't actually apply to any problem.

jadjare commented 8 years ago

Just read a little more of the Specification Pattern wiki article. Interestingly, as a side note, the Criticism section of the article actually points out the use of Getter only properties and well named methods as a good alternative to the pattern. It was good to see as it reinforces our decision to prefer methods over setters.

johncollinson2001 commented 8 years ago

The spec pattern is definitely for handling rules, but the scenario we've gone through in this post describes a rule - "a product cannot be deleted if there is an open order made for it". I don't think we'd handle this scenario with domain events so it was a bad example to use for the purpose of the main discussion point! (my bad!)

johncollinson2001 commented 8 years ago

In principle we agree that bounded contexts should not communicate with each other directly, but via an events system (e.g. domain events), however in practice we have not quite got there yet and it may not be as simple as this!

dibley1973 commented 8 years ago

Linked to #20