When I first learned about the Model-View-Presenter (MVP) pattern, I was excited. I thought this was The Pattern to use for Vaadin user interfaces and that it would make everybody’s life a lot easier. Now, when I am older and maybe a bit wiser, I am not so sure anymore.
As an architect, it is easy to draw up fancy designs and tell the developers “this is how we are going to do it”. However, at some point, when you actually have to do some coding yourself, you might realize that your fancy-pants designs are only useful for about ten percent of the use cases. For the rest of the use cases, they are over-engineered.
MVP is a vague pattern that can be interpreted and used in many different ways. In many Vaadin projects, I have found it is used in a suboptimal way, causing problems like the following:
- For each view in the application, you have to create and maintain at least three separate Java files: the view interface, the presenter class and the view class. You might have to change all of them just to make a small addition to a form.
- Pure MVP implementations do not allow the presenter to be aware of the existence of Vaadin. This makes some things more complicated than they need to be.
- The view concept in MVP is often confused with the view concept of the Vaadin navigation API. This in turn often leads to big views where a single presenter is responsible for managing everything inside the view.
- The role of the presenter and its relation to the view is not fully understood. This becomes especially clear when a large view is split up into subviews and subpresenters. The end result is often a mess of presenters and views calling each other or asking each other to call each other.
- Too much emphasis is being put on the VP part of MVP, ignoring the M part which is very important especially in complex UIs.
Even though not related to MVP, I have also run into the following problems:
- Presenter unit tests are easy to write, but are often very technical in nature: you first write the presenter, then you write a unit test that systematically goes through every line in the presenter. The result is a test that is several times bigger than the original class, took a lot of time and money to implement, has great testing coverage and provides little real value.
- The business logic tends to sneak into the presenters, where it definitely should not be.
These problems cannot be solved with a different UI pattern, but I just wanted to mention them here to make you aware of their existence (so that you can watch out for them in your own projects).
Related reading that might interest you
Download our free guide to the Future of Web Apps
Why use MVP?
I have not done any extensive research on this matter, but the most common reasons for using MVP in Vaadin projects today seem to be (in no particular order):
- It is MVP and cool kids use MVP.
- It makes it possible to replace Vaadin with a different UI framework without rewriting everything.
- It makes it possible to reuse the UI logic in both desktop and mobile UIs.
- It makes it easier to test the UI logic without setting up the entire runtime environment or simulating user interactions (clicks, text inputs, etc.).
- It separates the UI logic from the UI construction code.
The first reason is probably the same reason many applications use a relational database, passive entities and put all of their business logic in services or facades: because that is how you have always done it. It is really easy to fall back on tried-and-true approaches without actually thinking about whether it makes sense or not. Many times it does, but sometimes it does not. The intention of this blog post is to make you think about whether the current practice is actually the best one and whether there might be a better way to do some things.
The second and third reasons are more technical in nature. I am pretty confident these requirements are not very common in most projects, but if they happen to apply to your project, the classical MVP approach might be the right one for you. However, if you know you are only making a desktop application, or you know that your mobile and desktop applications will be so different that there is no point in trying to reuse any logic, or you know that you will stick to Vaadin throughout the project, then there is no point in spending time and money on something that will never happen.
I think the last two reasons are the most interesting ones, since they have to do with testability and separation of concerns. These are in my opinion signs of clean code and should be embraced, but there’s more than one way to skin a cat.
Testability
Let’s start with testability. Writing tests just for the sake of writing tests does not make any sense. There should be a clear purpose behind every test we write, and test coverage is not one of them. Let’s have a look at the most common types of tests:
Unit tests
- Tests a single component in complete isolation, mocking or stubbing the entire outside world
- Very good tool support
- Easy to write and run
Integration tests
- Tests how different components work together in an isolated environment. It can go through the entire stack or it can mock parts of the application (such as external services).
- Modern application containers like Spring and CDI introduce challenges as scopes and other runtime state must be controllable or mockable
- Harder to write and run than unit tests, but provide more value in my opinion
Acceptance tests
- Tests that the system is doing what the stakeholders expect it to do and nothing else
- Runs on the same level as the integration tests. If you can implement and run an integration test, you can also implement and run an acceptance test.
- The biggest difference between an acceptance test and an integration test is that an integration test concentrates on a particular function or method, whereas an acceptance test concentrates on a particular use case or user story
What kind of UI testing do we really need? How much time and money should we allow ourselves to spend on writing tests? What are the costs in developer productivity and complexity of design? I have seen UI implementations becoming a lot more complex just to make it easier to write tests for them. Is it really worth it? Would it be a better return of investment to hire a human tester to go through the use cases instead of spending time and money on trying to automate them? I do not know, but I think these questions are worth asking and should be asked in every project.
Separation of Concerns
I like separation of concerns. It brings order to chaos and makes your code cleaner and easier to read - when done right, that is. And doing separation of concerns right is often easier said than done.
In classic MVP, the separation is between the data (model), the UI construction (view) and the UI logic (presenter). I like to think of this as a horizontal separation of layers with clearly defined responsibilities and interfaces. However, what would happen if the separation was vertical? Let’s have a look at that.
Think About Components
What if developers moved away from thinking about their UIs in terms of views or pages and started to think more in terms of components? For a simple view, the view itself is a custom component that in turn contains existing Vaadin components. More complex views are split up into smaller custom components. Each component does one thing and does it well, and encapsulates both its UI logic and UI construction.
Start simple...
The starting point for a new custom component is a single class without any external presenter at all. The UI logic is implemented in separate protected or package protected methods within the component class. The methods can be aware of Vaadin and if you are using Java 8, you can implement them as listener methods that can be directly attached to components using method references.
The UI logic updates the UI by calling other protected or package protected methods within the component class.
Java 8 Example:
public class MyViewComponent extends VerticalLayout { private final Backend backend; // Constructor injected e.g. protected void init() { saveButton.addClickListener(this::save); } void showChangesSaved() { // UI update method // Show a notification, make a label visible, etc. } void save(Button.ClickEvent event) { // UI logic method backend.save(); showChangesSaved(); } }
This approach can be tested (using both unit and integration testing), if done in the following way:
- The test class and the component class both reside in the same Java package (but in different source folders). This makes it possible for the test to access package protected fields and methods.
- All dependency injections are done either as constructor parameter injections or protected/package protected field injections. This makes it possible for the test to replace dependencies with mocks and stubs as needed.
- All UI logic methods are protected or package protected so that the test can invoke them.
- All UI update methods are protected or package protected so that they can be stubbed in an anonymous subclass, if necessary.
- If the component is managed by a container such as CDI or Spring, and this cannot be easily controlled (for example due to different scopes), it is easier to test the component outside of the container.
- When performing integration testing, the backend can still be container managed. In that case, the test would fetch the necessary dependencies from the container, inject them into the component and run the test.
This approach does not separate the UI logic code and the UI construction code into separate classes, but it does keep them in separate methods. For small or simple UI components, this is good enough.
Then add complexity...
As a UI component grows and becomes harder to maintain, something has to be done. The first question a developer should ask is whether the component can be split up into smaller sub-components. If that is not the case, the next step is to separate the UI logic methods into their own, inner presenter class (this class can be either static or nonstatic, depending on your needs and personal preference). You can call this class whatever you like: Presenter, Controller, UILogic, etc. The most important thing to pay attention to is that the class is owned by and visible to the UI component only. The outside world knows nothing about its existence. Any public API methods that are available to other components delegate to the presenter when needed.
Java 8 Example:
public class MyView extends VerticalLayout { private final Presenter presenter = new Presenter(); private final Backend backend; // Constructor injected e.g. protected void init() { saveButton.addClickListener(presenter::save); } void showChangesSaved() { // UI update method // Show a notification, make a label visible, etc. } class Presenter { // Could be called whatever you want void save(Button.ClickEvent event) { backend.save(); showChangesSaved(); } } }
This approach can be tested in the same way as the previous one. The only difference is that the UI logic methods invoked on a presenter instance, so the instance should be visible to the test class.
This approach has a better separation of concerns than the previous one, since the UI logic and the UI constructor code are defined in separate classes. The code is still easy to maintain since everything is in a single Java file. The presenter class is declared inside the component class to prevent outside components from gaining access to the presenter.
Finally...
If having a nested presenter class (or whatever you want to call it) is not enough, and it is also not possible to split up the UI into smaller components, the nested presenter class can be moved out to its own top-level class. It is, however, still owned by the UI component and should not be exposed to the outside world (this can be achieved by making the presenter class package protected).
This is basically the MVP approach that is currently being used in many projects, except there is no view interface and the presenter is very much aware of the existence of Vaadin. This makes several things easier. For example, the presenter can use Containers, Items and Properties directly, take care of session locking, etc.
The presenter class should, however, not be allowed to directly access or create individual components. If this is a requirement, there is no point in having a separate presenter class at all and all of the UI logic should be put inside the component itself.
This approach can be tested in the “classical way” by instantiating the presenter class and mocking the view. Modern mocking frameworks can mock classes, so there is no need to introduce a view interface just to be able to mock it. The separation of UI logic and UI constructor code is even clearer in this approach compared to the previous one, but the owning relationship between the component and its presenter is not as clear. This makes it harder to hide the presenter from the rest of the world and presents opportunities for presenter misuse. Also, there are now two Java files to maintain instead of one, but this need not be a problem; a complex, component based UI also consists of multiple files.
But What About Complex UIs? How Do the Presenters Communicate With Each Other?
Presenters do not communicate with each other - period. A presenter is visible and accessible to its owing component only, if there is a presenter at all. Just like a custom component interacts with e.g. a TextField on the component level, it interacts with other custom components on the component level as well.
If a more decoupled way of communication is desired, an event based approach should be used where the components (or their presenters) subscribe and publish to a separate event bus:
If the UI consists of different components that show different views of the same data, it is not the presenters that need to communicate with each other. In that case, the components need to share a common model. Which brings us to the next section...
Focusing on the M in MVP
For some reason, a lot of emphasis has been put on the V and P parts of MVP. This is unfortunate, since the M-part is an important player in making complex UIs maintainable, easy to read and extendable.
The model maintains the state of the application. It could be the contents of a form, the rows in a table, the current selection, or a combination of all of them. UI components can access the model and subscribe to changes (either directly or via their presenters). Whenever the model is changed, the subscribers are notified and can update themselves accordingly.
The model can be implemented in different ways. It can extend java.util.Observable, use Java Bean PropertyChangeEvents, expose Vaadin Containers, Items and Properties directly or implement its own mechanism.
The model can also act as the boundary between the UI layer and the business layer. ln addition to holding the application state, the model would also be responsible for loading and storing it from/to the business layer. Whether this is a usable approach or not very much depends on the needs of the applications and the design of the business layer. It is, however, a good tool to have in the toolbox and one that should be considered more often than it is today. If you want to read more about this pattern, Google for Application Model or Presentation Model.
What About Framework Support?
I used to maintain my own MVP framework for Vaadin 6, but nowadays I think using an MVP framework should be discouraged. The reason for this is that it forces all of the UI components to use MVP in one way only - even in cases where a simpler design would be much more appropriate. The MVP principles should be lightweight enough so that they can be implemented from scratch in every project that uses them.
In Conclusion
The problem with MVP is that it is not very well defined and many people have their own ideas of how MVP should be used (and nobody dare suggest anything different!). When used incorrectly or for the wrong reasons in a Vaadin application, it makes the code more complex and harder to read and maintain.
However, if you think of MVP as more of a process than a pattern, it can make the code easier to read, maintain and understand. It makes it possible to start with the simplest possible solution and gradually add complexity when the need arises.
Since nothing comes for free, this approach comes with a tradeoff. As the coupling between the UI logic and the UI construction becomes tighter, it becomes more difficult to test.
Another challenge is for a developer to know which design to use at which point and when one design should be migrated to the next level, since all UI components might no longer be implemented in the same way within the same project. In this case, the senior developers of the team have an important role to play.
So is MVP a best practice when building Vaadin applications? I don’t think it is one per se. I do, however, think that you can borrow bits and pieces from it when you design your own application architecture. Just remember not to blindly adopt anything without first asking yourself why and going through the pros and cons.
Learn more about Vaadin
Discover the easiest way to build web apps in Java
Related reading that might interest you
Download our free guide to the Future of Web Apps