Skip to content

Instantly share code, notes, and snippets.

@robertpenner
Created October 31, 2011 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertpenner/1327579 to your computer and use it in GitHub Desktop.
Save robertpenner/1327579 to your computer and use it in GitHub Desktop.
Command Flow DSL Comparison
const saveFlow:Flow = new Flow(
{
saveSeq : new Sequence(
[
new Attempt(ValidateChanges).guard(NotQuitting).fail(ShowError),
new Attempt(ConfirmWithUser),
new Execute(BlockQuitting),
new Attempt(SaveData).fail(saveDataErrorSeq),
new Execute(StoreSavedData),
new Execute(UnblockQuitting)
]),
saveDataErrorSeq : new Sequence(
[
new Execute(ProcessSaveError),
new Execute(UnblockQuitting)
]),
deleteSeq : new Sequence(
[
new Attempt(ValidateDelete).guard(NotQuitting).fail(ShowError),
new Attempt(ConfirmWithUser),
new Execute(BlockQuitting),
new Attempt(DeleteData).fail(deleteDataErrorSeq),
new Execute(UpdateDeletedData),
new Execute(UnblockQuitting)
]),
deleteDataErrorSeq : new Sequence(
[
new Execute(ProcessDeletionError),
new Execute(UnblockQuitting)
]),
cancelSeq : new Sequence(
[
new Attempt(CheckForChanges).guard(NotQuitting).fail(ShowAdvice),
new Attempt(ConfirmWithUser),
new Execute(RevertData)
])
});
<?xml version="1.0" encoding="utf-8"?>
<Flow xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="org.roburntlegs.*">
<children>
<Sequence id="saveSeq">
<Attempt action="{ValidateChanges}" guard="{NotQuitting}" fail="{ShowError}"/>
<Attempt action="{ConfirmWithUser}"/>
<Execute action="{BlockQuitting}"/>
<Attempt action="{SaveData}" fail="{saveDataErrorSeq}" />
<Execute action="{StoreSavedData}"/>
<Execute action="{UnblockQuitting}"/>
</Sequence>
<Sequence id="saveDataErrorSeq">
<Execute action="{ProcessSaveError}"/>
<Execute action="{UnblockQuitting}"/>
</Sequence>
<Sequence id="deleteSeq">
<Attempt action="{ValidateDelete}" guard="{NotQuitting}" fail="{ShowError}"/>
<Attempt action="{ConfirmWithUser}"/>
<Execute action="{BlockQuitting}"/>
<Attempt action="{DeleteData}" fail="{deleteDataErrorSeq}" />
<Execute action="{UpdateDeletedData}"/>
<Execute action="{UnblockQuitting}"/>
</Sequence>
<Sequence id="deleteDataErrorSeq">
<Execute action="{ProcessDeletionError}"/>
<Execute action="{UnblockQuitting}"/>
</Sequence>
<Sequence id="cancelSeq">
<Attempt action="{CheckForChanges}" guard="{NotQuitting}" fail="{ShowAdvice}"/>
<Attempt action="{ConfirmWithUser}"/>
<Execute action="{RevertData}"/>
</Sequence>
</children>
</Flow>
public function defineSaveFlow()
{
var processSaveRequest:CommandFlow = new CommandFlow();
processSaveRequest
.addGuard(ApplicationIsNotQuitting)
.execute(ValidateChanges);
processSaveRequest
.from(ValidateChanges)
.afterEvent(ValidationEvent.VALIDATED)
.execute(OfferConfirmationDialog);
.afterEvent(ValidationEvent.NOT_VALIDATED)
.execute(ShowError);
.exit();
processSaveRequest
.from(OfferConfirmationDialog)
.afterEvent(ConfirmationEvent.CANCELLED)
.exit();
.afterEvent(ConfirmationEvent.CONFIRMED)
.executeAll([BlockQuitting, SaveData]);
processSaveRequest
.from(SaveData)
.afterEvent(DataServiceEvent.COMPLETED)
.executeAll([StoreSavedData, UnblockQuitting])
.exit();
.afterEvent(DataServiceEvent.ERRORED)
.executeAll([ProcessSaveError, UnblockQuitting])
.exit();
commandMap.mapFlow(processSaveRequest, ScreenActionEvent.SAVE_REQUESTED);
}
public function defineDeleteFlow()
{
var processDeleteRequest:CommandFlow = new CommandFlow();
processDeleteRequest
.addGuard(ApplicationIsNotQuitting)
.execute(ValidateDelete);
processDeleteRequest
.from(ValidateDelete)
.afterEvent(ValidationEvent.VALIDATED)
.execute(OfferConfirmationDialog);
.afterEvent(ValidationEvent.NOT_VALIDATED)
.execute(ShowError);
.exit();
processDeleteRequest
.from(OfferConfirmationDialog)
.afterEvent(ConfirmationEvent.CANCELLED)
.exit();
.afterEvent(ConfirmationEvent.CONFIRMED)
.executeAll([BlockQuitting, DeleteData]);
processDeleteRequest
.from(DeleteData)
.afterEvent(DataServiceEvent.COMPLETED)
.executeAll([UpdatedDeletedData, UnblockQuitting])
.exit();
.afterEvent(DataServiceEvent.ERRORED)
.executeAll([ProcessDeletionError, UnblockQuitting])
.exit();
commandMap.mapFlow(processDeleteRequest, ScreenActionEvent.DELETE_REQUESTED);
}
public function defineCancelFlow()
{
var processCancelRequest:CommandFlow = new CommandFlow();
processCancelRequest
.addGuard(ApplicationIsNotQuitting)
.execute(CheckForChanges);
processCancelRequest
.from(CheckForChanges)
.afterEvent(ComparatorEvent.CHANGES_FOUND)
.execute(OfferConfirmationDialog);
.afterEvent(ComparatorEvent.NO_CHANGES_FOUND)
.execute(ShowAdvice);
.exit();
processCancelRequest
.from(OfferConfirmationDialog)
.afterEvent(ConfirmationEvent.CANCELLED)
.exit();
.afterEvent(ConfirmationEvent.CONFIRMED)
.execute(RevertData)
.exit();
commandMap.mapFlow(processCancelRequest, ScreenActionEvent.CANCEL_REQUESTED);
}
@neilmanuell
Copy link

@robert in your versions, how are the Events that proceed each Attempt defined?
or will they be specific CommandFlowEvents?

@robertpenner
Copy link
Author

The Events in Stray's version are variations on pass/fail. I made this pattern more an implicit part of the DSL and left the implementation (Events, Signals, callbacks, etc.) out of it. There would be another entity that reads the flow data and executes it. Each command would send a notification to it saying "I'm done" or "I passed/failed". How that happens is flexible.

@neilmanuell
Copy link

OK, does that imply only a done or a pass/fail relationship for Attempts?
Will it allow more than those outcomes (ie this that or the other)?
Or would you consider that that would allow SpaghettiFlow to form?
The omission of the events certainly increases the clarity.

@Stray
Copy link

Stray commented Nov 1, 2011

They're not really intended to solve the same problem.

The event driven model I'm playing with is about more clearly expressing flow through a series of non-async Commands, where the services or models dispatch the events that move things on, and Commands don't know anything about outcomes. It avoids you having to chaotically map / unmap Commands as side effects of other Commands (hidden away) and makes that all explicit.

Robert's sequence is a much more ground-up restructuring of how you handle an async flow.

Outcome handling has to be done somewhere - and these take quite different approaches to where that is done.

I don't think we're proposing an either / or anymore - the intention is that the new CommandMap api will have a method something like

.mapProcess(ProcessClass).toEvent(.... )

which would allow anything which implements the CommandProcess API (whatever that turns out to be) be loaded and initialised in response to that event (or signal, or trigger).

At least that's how I'm imagining it at the moment - could turn out quite different :)

@neilmanuell
Copy link

OK, so the ideas are fluid and are moving faster than public discussions at the mo, so I'll butt out :)
My preference for CommandFlow is definitely for non-async command processes - less mess and confusion (for me at least) with dumb commands.
BTW Why would the CommandMap API have a .mapProcess method, and not utilise an EventProcessTrigger instead?

@Stray
Copy link

Stray commented Nov 1, 2011

Because the process would receive an instance of the CommandMap to do work on (so that it itself can map triggers).

So ICommandProcess might be something like

set commandMap(value:ICommandMap):void

start();
stop();

The command flow / sequence itself would be doing work on the CommandMap.

All we're mapping with mapProcess is what conditions would cause the flow to be instantiated and loaded.

Sx

@Stray
Copy link

Stray commented Nov 1, 2011

But - I should stress that Till is doing CommandMap stuff at the moment, I may be behind.

@neilmanuell
Copy link

I see...
I shall just have to wait.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment