How to decouple your business logic from UI components?

Monday, February 15, 2021

Introduction

UI components are sometimes messy. If the business logic is tightly coupled with the UI it is often very hard to write proper tests. The only way to make those components testable is by decoupling UI elements from the business logic. In this article, I want to show you how to achieve this decoupling and how to keep them separated by using different architectural and design patterns.

This is the second part of my series "How to minimize untestable code". If you haven't read the first part, definitely check it out here!

To understand how to decouple your code from the UI, I want to answer the following questions:

  • Why is it even useful to decouple business logic and UI components
  • Testing the presentation logic by using the Humble Dialog pattern
  • How to implement looser coupling with MVVM

Why is it even useful to decouple business logic and UI components?

If you recall, at the beginning of the first part, I showed you the following WPF example:

GIST: https://gist.github.com/adrinamin/1d1e7fb4349ad65b9fd8b9651cb54294.js

I tried to untangle the code inside the MainWindow class a little bit by extracting the business logic into a separate private method. However, the method CalculateCrossSum is still tightly coupled to the Send_Click event callback. Of course, in this specific case, this is not a big issue. But imagine a big enterprise application where UI framework and business logic are coupled together like this. And imagine someone needs to add a new feature or, even worse, fixing a bug! This can be a frustrating task. Especially when having no unit tests which is difficult to write if code is tangled up like this.

Separating Concerns

To achieve the decoupling of UI and business logic and writing testable and maintainable code, we need to separate the concerns. Separation of Concerns (SoC) is a design principle first mentioned by Edsger W. Dijkstra in his 1974 paper "On the role of scientific thought":

Let me try to explain to you, what to my taste is characteristic for all intelligent thinking. It is, that one is willing to study in depth an aspect of one's subject matter in isolation for the sake of its own consistency, all the time knowing that one is occupying oneself only with one of the aspects. We know that a program must be correct and we can study it from that viewpoint only; we also know that it should be efficient and we can study its efficiency on another day, so to speak. In another mood we may ask ourselves whether, and if so: why, the program is desirable. But nothing is gained —on the contrary!— by tackling these various aspects simultaneously. It is what I sometimes have called "the separation of concerns", which, even if not perfectly possible, is yet the only available technique for effective ordering of one's thoughts, that I know of. This is what I mean by "focusing one's attention upon some aspect": it does not mean ignoring the other aspects, it is just doing justice to the fact that from this aspect's point of view, the other is irrelevant. It is being one- and multiple-track minded simultaneously.

In essence, this explanation simply means to focus only on one thing at a time. Translating this thought to our code above its means, we need to split the above class into two distinct classes. One is responsible for the UI and the other one is responsible for the business logic. Dijkstra's explanation also tells us that one class must not necessarily ignore its dependencies but their underlying implementation is irrelevant.

If we separate our concerns inside our application, the components will focus only on one thing. Going back to our initial problem, this design principle provides us a guideline that we need to split the UI framework from the business logic. We extract the business logic into its separate class and hide its underlying implementation from the UI components. Thus, we are to test our business logic independently from the UI.

So how to split our above example into UI component and business logic component. For that, let's start with a basic approach by using the Humble Dialog pattern.

Testing the presentation logic by using the Humble Dialog pattern

The Humble Dialog is a variation of the Humble Object pattern (see Xunit Test patterns by Gerard Meszaros, page 706). To implement this pattern, we move the business logic out of the UI component into a testable component. If we want to update the view, we pass the Humble Dialog as an argument. Thus, it is possible to create a mock object of the Humble Dialog when unit testing the business logic.

What does this mean for our initial WPF example? Well, we would move the CalculateCrossSum method into a separate class, called CrossSumCalculator.

GIST: https://gist.github.com/adrinamin/948c16b0c511e020ab49cfeac4e34d76.js

This class is easy to test and there is no dependency on any UI component. If you recall, the CalculateCrossSum method had a static MessageBox.Show call from the System.Windows UI library. Now, we wrap the call with the MessageHandler and inject [link to dependency injection] its interface into the CrossSumCalculator as a dependency.

GIST: https://gist.github.com/adrinamin/2eb012140af8d999e69ce94b07582589.js

If we now want to write Unit Tests, we simply submit a Mock Object of the IMessageHandler into the class.

How does the refactored MainWindow look like? It becomes a Humble Dialog, similar to a simple shell that is only responsible for UI related logic. We inject the interface of the CrossSumCalculator class [link to dependency injection] into the MainWindow class to use the cross sum functionality.

GIST: https://gist.github.com/adrinamin/43df700a270183d3359fcf61a7839fc2.js

As you can see, we hide the cross sum implementation behind the ICrossSumCalculator interface. It is not the MainWindow's concern to handle this logic. It only handles the UI related functionality. The extraction separates the concerns of the application and we can now write unit tests for our business logic.

How to implement looser coupling with MVVM?

The Humble Dialog pattern is a simple pattern to extract business logic from UI components. The approach is straight forward and provides immediate improvements for testability.

Going forward, enterprise applications need more than simple extractions of business logic. You need to provide a distinctive architectural pattern to provide a clear separation of concern for every layer inside your app. One possible pattern is the Model-View-ViewModel (MVVM) architectural pattern. It guides you to write well-structured applications. It separates your application into three parts:

  • The Model contains the client data.
  • The ViewModel for exposing and manipulating the Model's data.
  • The View defines the structural definition of what the user sees on the screen.
Basic structure of the Model-View-ViewModel architectural pattern

The MVVM architectural pattern alone would fill a complete blog article. Therefore I just want to focus separation of UI and business logic.

As you can see in the diagram above the data resides inside the Model Objects. The ViewModel is a separate layer in between which defines how the data is displayed. At this layer, you call the business or service layer. The View uses the ViewModel to display the data through Data Binding. In short, Data Binding is a technique to bind and synchronize, data sources and consumers.

The advantage of this architectural pattern is the clear separation of concerns. There is no business logic inside the UI. Writing code tests for the business logic is easy due to the loose coupling.

So how would this look like if we are trying to implement the MVVM pattern for our cross sum calculation example? As you recall, our current solution of the MainWindow class looks like this:

GIST: https://gist.github.com/adrinamin/43df700a270183d3359fcf61a7839fc2.js

First, we need to restructure our application. To keep the structure simple, we use the MainWindow as our View. It is a simple Window containing two TextBox fields and a Button.

The cross sum calculator application

The XAML is kept as simple as possible for demo purposes.

GIST: https://gist.github.com/adrinamin/3097313f546542a72eec4bd20af54d39.js

We use only one corresponding ViewModel for the View, called MainWindowViewModel. Of course, you could write several ViewModels depending on your needs but in our case, this is not necessary.

GIST: https://gist.github.com/adrinamin/b0505d0b2c42447a0d2978aea678de48.js

The ViewModel contains two public properties, Number and Name, and a public command, called CalculateCommand. The MainWindowViewModel injects the ICrossSumCalculator interface and the IMessageHandler for notifying the user about the result.

The MainWindow uses the  ViewModel's public members through Data Binding. Thus the code-behind of the MainWindow class completely disappears and it only sets the DataContext to the MainWindowViewModel.

GIST: https://gist.github.com/adrinamin/583ef36954fe5b265209d5b6fba236fb.js

We keep the newly created CrossSumCalculator class as it is and we move it into a separate Service folder inside the WPF project.

The application folder structure

In bigger scenarios, you could move the CrossSumCalculator into a shared service library to access this functionality from different clients.

The MVVM implementation of the cross sum calculator app

We now have a complete separation of concerns. The View is only responsible for displaying information. The ViewModel handles the interaction logic and defines how the data is displayed. The real business logic is extracted into a separate service class and is independently testable. And due to the fact, that we inject the interfaces of ICrossSumCalculator and IMessageHandler into the ViewModel, we could also write automated code tests for the MainWindowViewModel (if necessary).

Conclusion

UI components are too volatile to be included in tests. Patterns like the Humble Dialog or MVVM provide great decoupling between business logic and UI framework. The Humble Dialog pattern is another way to abstract UI components from business logic. Thus we can test our business logic without the interference of UI components.  The MVVM architectural pattern further separates concerns for the entire application architecture. It provides loose coupling between components and minimizes the risk of untestable code in your application.

Finally, we can say that we need to minimize untestable code inside our app to reduce the risk of bugs and to make it easy to implement new features.

What do you think about minimizing untestable code? How often did you encounter untestable code in your developer life and what did you do to resolve this issue? I'm excited to hear your approach.

Let us know what you
think about this article