Event-driven design with delayed consequences

  softwareengineering

EDIT: more direct situation

I need to design a program that will create particular objects and run computationally intensive procedures using its fields in order to update other fields. When a procedure completes, other procedures can now be launched using the new fields.

These objects are saved to disk and used in later stages of the software.

The problem is that some further procedures need to wait for multiple prior procedures before executing, so none of those “knows” when to trigger the next steps. These procedures are applied to various objects at various stages of their lifetime, so it would be awkward for all procedures to check for all possible consequences upon completion (at least, in the way I am imagining it at the moment).

Since the procedures and the objects are decoupled, event-driven design seemed like a natural fit. However, the delayed conditions of some events make this less obvious to design. It seems like some events would need to “accumulate” before triggering a response.

Is there a clean way to do this with event-driven design? If not, what would be the suitable design for this type of problem?

2

Some part of your system needs to have the knowledge which fields of which objects are required before a certain procedure can be started. Usually, this knowledge is bundled with the procedure itself, so lets assume Procedure is an abstract class or interface providing at least the following method:

 abstract class Procedure
 {
      bool ExecuteWhenPreconditionsFulfilled(/* context reference*/)
 }

Now, you either need events which are emitted whenever the execution of a procedure ends, or whenever one of the relevant fields of the mentioned objects is set to its final values. The Procedure object needs to subscribe to the events of all other procedures which have to run before, or to all “FinalValueSet” events of the forementioned objects/fields.

For example, when procedure C requires two specific fields set by procedure A and B, subscribe C to the “ExecutionEnded” event of A, and also to the to the “ExecutionEnded” event of B. That way, ExecuteWhenPreconditionsFulfilled will be called two times. At the first time, preconditions are not fulfilled and nothing will happen. At the second time, preconditions are fulfilled and C will start its execution exactly at the time when both A and B have ended, regardless of which one ends first.

This is just a rough scetch, and you need to flesh out the details by yourself. For example, you may separate the test when the preconditions are fulfilled from the start of the execution. You will have to think about how to handle the execution in different threads or processes, and make sure different “ExecutionEnded” events calling ExecuteWhenPreconditionsFulfilled will not produce some race condition. You will also have to think about how to handle lifetime and scope of the “particular objects” and how to provide access to them for the different procedures.

It sounds like you have a lot of conditions going on here, not a bad thing, but I think you will definitely need to ensure that your events are organized. Spend some time and diagram out your sequence of events if they’re consistent, otherwise make truth tables or state diagrams of what should happen, leave nothing to chance.

Consider something like an EventBroker pattern to facilitate all of your possible events that could come up, and lighten the load of the tasks themselves to simply report “ProcessingCompleted”, then of course your EventBroker can fire off all needed tasks based on the result. And you should be able to easily unit test your process task and your event broker to find any issues as a bonus to ensure that you have a locked in structure or that your processes are interacting correctly.

Is there a clean way to do this with event-driven design?

Yes. Queue up a “query” or “interact” event, and attach the backlog fields.
This might arrange for an RDBMS query to eventually be satisfied,
or a user interaction with the GUI to eventually be satisfied.
Once that happens, you have all the necessary inputs gathered together,
allowing the update to proceed to successful completion.

LEAVE A COMMENT