UX Orchestration
2 years ago
Software architecture is an interesting subject and discipline that's often focused on large-scale problems: distributed computing, cloud computing, and, more generally, server applications. But analysis and design are critical in all contexts – especially in the regularly overlooked area of GUI applications.
Your first reaction to this might be: "Wait a minute, I've heard about this before. Isn't Model/View/Controller (MVC) the answer to GUI application architecture?" Well, yes . . . and no. MVC is a design pattern for developing user interfaces (UIs). The missing context there is that "user interfaces" in practice tends to refer to the implementation of GUI widgets within a GUI framework. It is not about how to architect and organize the internals of UI applications based upon those widgets/framework. In fact, this topic is ignored in the documentation of some of the best GUI frameworks available today – the apparent logic being that GUI application architecture is solely at the discretion of, and is the responsibility of, the developer.
So, we seem to have a blank canvas to paint on – and many developers would take the stance that this is good and appropriate as there is no common architecture for all GUI applications. GUI applications are so diverse (spreadsheets, document editors, messaging, games, etc.) that no one architecture could possibly apply.
But is this really true? Given the prevalence of MVC, nearly all user interfaces decompose into widgets provided by a framework at some point. The primary function of a GUI application is composing these widgets into organizational groupings – what I'll call "views." Views are often realized and referred to as dialogs, screens, panes, pages, etc. and are often focused on a particular user experience task.
Therefore, an architectural pattern that addresses the organization and management of views should indeed be generally applicable.
Good GUI Applications Are Surprisingly Hard
One of the most important (and often overlooked) characteristics of GUI applications is that they manage an immense amount of state. At any given moment they are acting on concerns like:
- Which views should currently be presented and focused.
- Constructing, configuring and displaying a view and its widgets.
- Responding to user input and changing the state of widgets.
- Loading and modifying data from files and/or external services.
- Displaying or hiding views, often including animation.
Therefore, an effective GUI application architecture must focus on organizing and managing this state. Some GUI frameworks address this need with what are sometimes referred to as "controllers." But they do so superficially, with controllers typically presented as a mechanism to manage a single view. They almost never touch on how to organize, control, coordinate and manage multiple controllers within a single application.
Introducing UX Orchestration
In this context, let's introduce the concept of user experience (UX) orchestration, and its fundamental building block, UX state. UX orchestration is the logic that implements the collection of UI concerns mentioned in the previous section – i.e., the orchestration of the application's UI to create the UX. It's easy to think of an application's UI as a static thing that's built once and then interacted with by the user. But in reality, the UI is composed of various views, each in different states. UX orchestration's task is to organize and configure the whole of this state and carefully control how it transforms to a new configuration as defined by the user experiencing it.
A UX state represents a stable configuration of views within a GUI application. It is a software construct (typically an "object" in object-oriented languages) whose purpose is to orchestrate one or more views into a single configuration of the UI of the application. Whereas the role of widgets is to implement user interface, and the role of views is to organize and control widgets, the role of UX states is to orchestrate views.
For instance, when an application launches, the default UI presentation could be considered the "root" UX State. If the user chooses "Open File" from a menu, the application instantiates and adds a new UX state of "file dialog." UX states are organized in a tree with the "root" UX State at the root of the tree (thus the name). Any new UX state is a child of some existing UX state in the UX state tree. In this example, the file dialog state is a new child state of the root state since it represents additional UI layered on top of the default UX state. The UX state tree represents the entire configuration of the UI at any particular moment.
Each UX state is responsible for instantiating and presenting their views on "entry" to the state and hiding and destroying them on "exit" from the state.
The Benefits of UX State
In this example, the amount of overhead introduced by a layer of UX states might seem dubious. But consider the benefits that arise in more complex scenarios:
Views can be isolated from the context in which they are invoked. For instance, an editor view might be invoked in a dialog from a button on an object detail page or could be displayed in a popup from a right click on a graphical representation of a larger document. The UX state orchestrating the editor onto the screen can isolate the editor itself from how it is mechanically presented on the screen. This allows the view to maintain a tight focus on the editing function.
View dependencies such as integrations with data sources and other types of global managers within the application can be moved from views to UX states. This further isolates the view from burdens not related to the editing function.
UX state enables the developer to avoid a common nuisance in navigation based applications – i.e., a stack of views that can be pushed and popped to. Without a UX state approach, the views tend to get tightly coupled with each other – if view B gets pushed on after an action on view A, view A often contains code that will instantiate and push view B onto the stack. With a UX state approach, this stack manipulation moves to the UX state, making the inevitable rearranging of view sequences simpler.
As UX states become a place to manage how views transition onto or off of the screen and how any currently-displayed view communicates with the rest of the application and its external resources, views become much easier to test. Building a mock for a UX state to test a view is a relatively straightforward process.
The UX state tree can be visualized and inspected at any moment, simplifying application design and debugging. Instead of laboriously attempting to prevent spaghetti knots of paths through ad hoc UI configuration code, you can design and define precise UI configurations with well-defined transitions between them. This streamlines implementation and debugging for developers while also making it easier for users to understand how the application functions and can be controlled.
Up to this point, this discussion hasn't focused on any technical details. UX state is an architectural design pattern that works in almost any language and with any GUI framework. It's suitable in a web browser (SPA), a desktop application, or a mobile application, and it's independent of an application's purpose.
Up Your Game
Just because you aren't intentionally orchestrating the UI views of your application doesn't mean some form of orchestration isn't already happening. It just means it's happening in a way that likely leaves room for significant improvement.
I've used this architectural pattern for more than 5 years, in more than 10 projects and in 3 different languages and GUI frameworks. The benefits I've experienced and cataloged above are evidence to me of the utility of this approach. I believe recognizing UX orchestration as an important and common aspect of GUI application development has been overlooked for too long and that GUI application developers should seriously consider incorporating this approach into their projects.