Is there a future for component architecture?
- Tutorial
Component frameworks allow you to quickly cost applications using off-the-shelf building blocks - components. They allow you to quickly build applications of small and medium complexity, but it is very difficult to create large, flexible and customizable applications. Also, as the application develops, it becomes harder and harder to adapt to new customer requirements. The purpose of this article is to find out the causes of these problems and find a suitable solution.
Components - Building Blocks Application
Components are the primary means of expanding the functionality of an application. They are intended for repeated use, have a specific interface and interact with the software environment through events. The process of creating components is somewhat different from creating an application based on them. The component should not only contain useful functionality, but also be designed from the very beginning for reuse.
Reusing components
For components to be reusable, they must be designed according to the principle of weak binding. To do this, many frameworks implement an event model based on the Observer template. It allows multiple recipients to subscribe to the same event.
The browser is a kind of intermediary that stores a list of recipients. When an event occurs inside the component, it is sent to all recipients on this list.
Thanks to the intermediary, the component does not know about its recipients. And recipients can subscribe to events from different components of a certain type.
Using components is easier than creating them.
Using components, you can quickly create different forms, panels, windows and other components of the interface. However, in order to reuse new constituent elements, they must be turned into components.
At what cost is this achieved? To do this, you need to determine what external events the component will generate and be able to use the message sending mechanism.
Those. you need to create at least new event classes and define interfaces or callback methods for receiving these events. This approach adds complexity to the implementation of reusable application elements (forms, panels, windows, pages). Well, if in a system with a dozen constituent elements. You can still follow this approach somehow. But what if a system consists of hundreds of elements?
If you do not follow this approach, it will lead to strong binding between the elements of the system and will nullify the chances of their reuse. And this, in turn, will entail duplication of code, which will complicate support in the future and lead to an increase in errors in the system.
The problem is compounded by the fact that component users often do not know how to identify and send new events for their constituent elements. But at the same time, they can easily use ready-made events defined in the component framework. Those. they know how to receive events and not how to create and send them.
To solve this problem, let's look at how to simplify the use of the event model in the application.
Too many Event Listeners
Java Swing, GWT, JSF, Vaadin uses the Observer pattern to implement the event model. Where many recipients can subscribe to one event. The implementation here is a list to which Event Listeners are added. When an event occurs, it is sent to all recipients from this list. Each component creates its own set of Event Listeners for one or more events.
This leads to an increase in the number of classes in the application. Which, in turn, complicates the support and development of the system.
For example, in Java, this was before annotations. With annotations, it has become possible to sign methods to specific events. An example is the implementation of the event model in the CDI ( Contexts and Dependency Injection ) from Java EE 6.
public class PaymentHandler {
public void creditPayment(@Observes @Credit PaymentEvent event) {
...
}
}
public class PaymentBean {
@Inject
@Credit
Event creditEvent;
public String pay() {
PaymentEvent creditPayload = new PaymentEvent();
// populate payload ...
creditEvent.fire(creditPayload);
}
}
And also the implementation of Event Bus in Guava Libraries:
// Class is typically registered by the container.
class EventBusChangeRecorder {
@Subscribe public void recordCustomerChange(ChangeEvent e) {
recordChange(e.getChange());
}
}
// somewhere during initialization
eventBus.register(new EventBusChangeRecorder());
// much later
public void changeCustomer() {
ChangeEvent event = getChangeEvent();
eventBus.post(event);
}
As a result, there is no need to implement many Event Listeners for your components. Using events in the application has become much easier.
Using Event Bus is convenient when application components are simultaneously placed on the screen and exchange messages through it, as shown in the figure below.
The title, the menu on the left, the contents in the middle, the panel on the right, all of these elements are automatically converted into reusable components using EventBus. Because they do not depend directly on each other, but use a common bus for communications.
Subscribed to events - do not forget to unsubscribe!
Replacing Event Listeners with annotations - a big step forward was made to simplify the use of the event model.
However, in order for the component to receive events from the Event Bus, it must be registered. And so that he stops receiving events, then remove it from the list of recipients. Who should take on these responsibilities?
Also subscribe to his events and, more importantly, unsubscribe from them at the right time. It is quite possible that the same recipient subscribes to the same event several times. This can lead to many repeated alerts. It is also possible that many different system components subscribe to one event. In this case, one event can cause a series of avalanche events.
To better control the event model, you can put the work with events into the configuration and assign the responsibility of managing events to the application container. Because Since certain events are available only under certain application states, it is reasonable to place this state management in the configuration as well.
The controller, guided by the configuration, signs the corresponding screens for events in the Event Bus, depending on the current state of the system.
Finite State Machines were just designed for this purpose. They also have states within which events occur. And events that trigger transitions between states.
Benefits of using Finite State Machines to configure application states.
Application configuration in most cases is set statically. By setting up the application using, for example, dependency injection, we specify how the application will be structured at startup. At the same time, we forget that in the process of using the application, its state changes. The change of state is often hardcoded in the application code, which entails the difficulty of changing it and further support.
By bringing transitions between states into a configuration, we get a new level of flexibility in building systems. And therefore, at the stage of creating composite elements of the application, such as forms, windows, panels, we can not worry about what state the application should go into. This can be done later by configuring the behavior in the configuration.
Moreover, all components of the system can communicate using a unified mechanism for sending events - through the controller (Statemachine).
This approach turns all the components of the application (forms, windows, panels) into reusable components that can be easily controlled using an external configuration.
How to use Statemachine effectively to configure the application will be described in the next article.
PS If you are interested, you can look at configuration examples in Enterprise Sampler.
It would be interesting to know how you see the future of component architecture and its role in creating applications?
PPS A new article has been released detailing how to use this solution: Navigation: an implementation option for a corporate application