uncategorized

UI Workflow is business logic

Over my years as a programmer I’ve focussed a lot of attention and energy on business logic. I’m sure you have too. Business logic is, after all, a huge part of what our clients/end users want to see as an output from our development efforts. But what is included in business logic? Usually we think of all the conditionals, looping, data mangle-ment, reporting and other similar things. In my past experiences I’ve poured immense effort into ensuring that this business logic was correct (automated and manual testing), documented (ubiquitous language, automated testing and, yes, comments when appropriate) and centralized (DDD). While I’ve had intense focus on these needs and practices, I’ve usually neglected to recognize the business logic that is buried in the UI workflow within the application.

On my current project I’ve been presented with an opportunity to explore this area a bit more in depth. We don’t have the volume of what I have traditionally considered business logic. Instead the application is very UI intensive. As a result I’ve been spending a lot more time worrying about things like “What happens when the user clicks XYZ?” It became obvious to us very early on that this was the heart of our application’s business logic.

Once I realized this we were able to focus our attention on the correctness, discoverability, centralization and documentation of the UI workflow. How did we accomplish this then? I remember reading somewhere (written by Jeremy Miller I think, although I can’t find a link now) the assertion that “Every application will require the command pattern at some point.” I did some research and found a post by Derick Bailey explaining how he was using an Application Controller to handle both an Event Aggregator and workflow services. To quote him:

Workflow services are the 1,000 foot view of how things get done. They are the direct modeling of a flowchart diagram in code.

I focused on the first part of his assertion and applied it to the flow of user interfaces. Basically it has amounted to each user workflow (or sequence of UI concepts) being defined, and executed, in one location. As an example we have a CreateNewCustomerWorkflowCommand that is executed when the user clicks on the File | Create Customer menu. It might look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public  class  CreateNewCustomerWorkflowCommand  : ICommand <CreateNewCustomerWorkflow >
{
private readonly ISaveChangesPresenter _saveChangesPresenter;
private readonly ICustomerService _customerService;
private readonly ICreateNewCustomerPresenter _createNewCustomerPresenter;

public CreateNewCustomerWorkflowCommand(ISaveChangesPresenter saveChangesPresenter,
ICustomerService customerService,
ICreateNewCustomerPresenter createNewCustomerPresenter)
{
_saveChangesPresenter = saveChangesPresenter;
_customerService = customerService;
_createNewCustomerPresenter = createNewCustomerPresenter;
}

public void Execute(CreateNewCustomerWorkflow commandParameter)
{
if (commandParameter.CurrentScreenIsDirty)
{
var saveChangesResults = _saveChangesPresenter.Run();
if (saveChangesResults.ResultState == ResultState .Cancelled) return ;
if (saveChangesResults.ResultState == ResultState .Yes)
{
_customerService.Save(commandParameter.CurrentScreenCustomerSaveDto);
}
}

var newCustomerResults = _createNewCustomerPresenter.Run();
if (newCustomerResults.ResultState == ResultState .Cancelled) return ;
if (newCustomerResults.ResultState == ResultState .Save)
{
_customerService.Save(newCustomerResults.Data);
}
}
}

As you can see the high level design of the user interaction, and service interaction, is clearly defined here. Make no mistake, this is business logic. It answers the question of how does the business expect the creation of a new customer to occur. We’ve clearly defined this situation in one encapsulated piece of code. By doing this we have now laid out a pattern whereby any developer looking for a business action can look through these workflows. They clearly document the expected behaviour during the situation. Since we’re using Dependency Injection in our situation, we can also write clear tests to continuously validate these expected behaviours. Those tests, when done in specific ways, can also enhance the documentation surrounding the system. For example, using BDD style naming and a small utility to retrieve and format the TestFixture and Test names we can generate something like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public  class  When_the_current_screen_has_pending_changes
{
public void the_user_should_be_prompted_with_the_option_to_save_those_changes(){}
}

public class When_the_user_chooses_to_cancel_when_asked_to_save_pending_changes
{
public void the_pending_changes_should_not_be_saved(){}
public void the_create_new_customer_dialog_should_not_be_displayed(){}
}

public class When_the_user_chooses_not_to_save_pending_changes
{
public void the_pending_changes_should_not_be_saved(){}
public void the_create_new_customer_dialog_should_be_displayed(){}
}

public class When_the_user_chooses_to_to_save_pending_changes
{
public void the_pending_changes_should_be_saved(){}
public void the_create_new_customer_dialog_should_be_displayed(){}
}

public class When_the_user_chooses_to_cancel_from_creating_a_new_customer
{
public void the_new_customer_should_not_be_saved(){}
}

public class When_the_user_chooses_to_create_a_new_customer
{
public void the_new_customer_should_be_saved(){}
}

As you can see, this technique allows us to create a rich set of documentation outlining how the application should interact with the user when they are creating a new customer.

Now that we’ve finished implementing this pattern a few times, have I seen any drawbacks? Not really. If we didn’t use this technique we’d still have to write the code to coordinate the screen sequencing. That sequencing would be spread all over the codebase, most likely in the event handlers for buttons on forms (or their associated Presenter/Controller code). Instead we’ve introduced a couple more classes per workflow and have centralized the sequencing in them. So the trade off was the addition of a couple of classes per workflow for more discoverability, testability and documentation. A no brainer if you ask me.

Is this solution the panacea? Absolutely not. It works very well for the application that we’re building though. In the future will I consider using this pattern? Without doubt. It might morph and change a bit based on the next application’s needs, but I think that the basic idea is strong and has significant benefits.

A big shout out to Derick Bailey for writing a great post on the Application Controller, Event Aggregator and Workflow Services. Derick even has a sample app available for reference. I found it to be great for getting started, but it is a little bit trivial as it only implements one simple workflow. Equally big kudos to Jeremy Miller and his Build Your Own CAB series which touches all around this type of concept. Reading both of these sources helped to cement that there was a better way.