1 Introduction

The birth and the evolution of the Internet, from 2 million users and 1 Terabyte/month of traffic in 1990 to over 2 billion users and 10 Terabyte/second of traffic in 2011 [21] has sparked the development of more and more complex, large-scale, data-intensive software architectures that provide services to millions, even billions of people. At these magnitudes, monitoring quality of service [1, 10, 19] become even more critical. For this reason, a renewed attention recently emerged on how services should be designed to increase a property we call observability, that is, the ability to constantly and incrementally observe individual components, their granular interactions, the atomic computations and their intermediate and final results at runtime. Catering for observability allows more specific and granular refactoring, thus enabling the continuous, incremental, and iterative improvement of their functionality in a DevOps fashion [3].

Starting by discussing the suitability of a classical architectural patterns like Model-View-Controller (MVC) [4] for observability, this article contributes an attempt at offering a more observable version of the MVC software architectural pattern that is more consistent with observability requirements for architectures in our modern DevOps contexts. We named our research solution as oMVC, which stands for “observable Model-View-Controller”. To evaluate our research solution, we operate a proof-of-concept experiment for \(o\)MVC implementing the architectural pattern within a medium online multiplayer game of our own design. Concerning this proof-of-concept, we discuss how the \(o\)MVC abstractions and data-flow improve observability. Subsequently, we evaluate the additional maintenance costs of adding-in observability. More specifically, we use metrics from the state of the art in software maintenance [6] to measure the maintainability of our proof-of-concept with and without \(o\)MVC — this evaluation shows rather conflicting results.

On one hand, results indicate that the adoption of an improved architectural pattern, such as \(o\)MVC, is a trade-off between the complexity of the pattern. On the other hand, results imply that the effort required in the post-design phase of the software development, operations, maintenance and refactoring, testing, and long-term maintenance are also higher.

We conclude that observability can bring about many benefits but, like any other architecture property, it needs careful trade-off analysis [12, 22], e.g., especially in (micro)services design contexts where multiple instances of more observable architectural patterns are usedFootnote 1.

Structure of the Paper. Section 2 describes the state of the art. Section 3 defines Service Observability and outlines the proposed solution: \(o\)MVC. Section 4 evaluates our research solution in terms of software metrics. Section 6 concludes our paper.

2 State of the Art: MVC and Its Variants

MVC - Classic Approach. MVC is a software architectural pattern originally introduced in the SmallTalk-80 programming language for the design and implementation of user interfaces [13]. Since its introduction in 1988 as a paradigm for building user interfaces, MVC has progressively shifted towards being a general architectural pattern for complex software architectures, especially used in web-services, and service applications designs [16,17,18]. The pattern defines the following three fundamental abstractions:

  • Model groups together the “architecture’s domain state and behaviour” [13] and “manages the behaviour and data of the architecture domain, respond to requests for information about its state (usually from the view), and responds to instructions to change state (usually from the controller)” [4]. In other terms, Model provides an abstraction that puts together the state of an architecture and the procedures manipulating the architecture state, everything in the context of each architecture’s domain.

  • View “deals with everything graphical; it requests data from their model, and displays the data.” [4]. In other terms, View provides an abstraction to represent to the external world the state of an architecture.

  • Controller “takes over the user interaction with the model and the view” and “contains the interface between their associated models and views and the input devices (keyboard, pointing device, time)” [4]. In other terms, Controllers coordinate Views and Models with the interactions produced by the user with the architecture.

Figure 1 illustrates the relationships between the different abstractions of MVC: the user interaction is managed by a Controller, which reacts to it by commanding the right Model to change the part of the state of the architecture it is responsible for; the Model notifies the Controller and the View that “depends” on it, so that they can either adapt their functionalities to the change occurred in the Model (this happens in the case of the Controller), or they render a new representation of the Model data to the external world (this happens in the case of the View). To render such data, a View requires to query their Models.

Fig. 1.
figure 1

Data-flow in MVC.

MVC Variants. The first variant we report for MVC is known as Model-View-ViewModel (MVVM) and is a software architectural pattern developed at Microsoft Inc. by Ken Cooper and Ted Peters for building event-driven user interfaces [7]. MVVM defines one additional abstraction in addition to the classical Model and View abstractions from classical MVC.

ViewModel. A ViewModel represents an intermediary between the View and the Model. It decouples Views from the Model by means of a local state, used by the Views to render a proper UI, and by means of a set of commands used by the Views to signal the occurrence of a user interaction. The execution of these commands causes the ViewModel to coordinate the realisation of different parts of the business logic of the architecture so that a new state for the architecture is generated. Whenever the internal data of the Model change, the ViewModel is notified and its local state is updated; in addition the Views associated to the ViewModel are notified and re-render pieces of UI to be displayed to the user (note that Views can be notified by the ViewModel not necessarily whenever the ViewModel’s local state changes). The View interacts with the ViewModel by means of data-binding and commands to render a proper UI and to signal user interactions. The ViewModel reacts to commands by coordinating pieces of business logic executed by the Model. The ViewModel reacts to newly available Model data by updating its local state and by notifying associated Views about the change. The notified Views automatically query the local state of the ViewModel to display an updated UI.

Model-View-Presenter (MVP). MVP is a software architectural pattern developed by Taligent Inc. in 1996 that defines the following abstractions: Model, Command, Selection, View, Interactor and Presenter. Model is defined as the same abstraction offered by MVC but it is only focused the architecture domain data or, equivalently, the state of the architecture; the architecture behaviour is outsourced to the Command abstraction, which offers an interface for updating slices of the Model. Selection represents elements of the architecture state, or of the Model, that Commands are willing to change. View is defined as the same abstraction offered by MVC but, in this case, it is also capable to capture user interactions, by means of Interactors (in MVC user interaction is captured by the Controller). Presenter is the coordinator of all the previous abstractions.

Summary. Observability of software artifacts seems considerable. More specifically, designing applications that are inherently observable has tangible benefits in terms of product maintainability, both in the implementation phase and later in the post-release phase. Indeed, a more observable application allows for a more accurate code analizability throughout all the life-cycle of the application and, hence, a reduction of the costs. Therefore, we aim at defining a precise set of features that applications have to fulfill, in order to be observable. In addition, we design an new architecture pattern that enforces these features by construction. The baseline stems from the following principle: observability is a precursor for the possibility of: (i) reading all the relevant information defining the application state and (ii) keeping track of the changes applied to the application state and of the order among the changes throughout the execution. In our analysis, we focus on the so-called stateful applications, that are such that the application functionality can only be obtained with the maintenance and manipulation overtime of different pieces of internal information – i.e., the application state.

3 Redesigning MVC for Observability

The analysis of the architectural patterns and frameworks in the previous section allowed us to elicit 5 common properties, three of them characterizing the new architectural solutions emerging to make an architecture observable.

For design patterns to aid service observability, they should offer suitable abstractions for:  

P1::

the representation (i.e., logging and reporting) of the architecture or service state, intended as a human- and machine-readable representation of the set of variable values (or simply, the observable variables) in which the system is observed to be in any given instant during operation [2, 14]. Note that, with the concept of variable, we identify any entity that can change value in the scope of the service’ own computation (e.g., an integer variable or even a more complex object);

P2::

the representation of the events that reflect data inputs from outside of the service and the internal events representing the effects over that data, including any of its computations – observability reflects an ordered set of data transformations [8] affecting the application state;

P3::

the manipulation of the architecture state in response to the events – observability reflects the value of architecture self-organization or other self-* properties [15] enacted in response to the context variations (e.g., inputs, specific processing results, etc.);

P4::

the provisioning of the architecture state to the outside environment in machine-readable format - in this case, observability reflects the ordered set of outputs that the system has produced against the inputs corresponding to those outputs;

P5::

the coordination of all the previous features - in this case, observability is realized by appropriate business logic inside the architecture;

  Stemming from the above, we offer the definition below:

figure a

3.1 Observability Limitations for MVC and Its Variants

Based on the previous terms and definitions, we classify the architectural patterns discussed in Sect. 2 in terms of the properties P1–P5. MVC partially supports P1 and P3 as it does not define two distinct abstractions for the architecture state and for the procedures manipulating the state: Model actually groups both together. Moreover, in MVC there is no abstraction to represent external events as the state transition is enabled by generic messages, exchanged by Controller and Model. Hence, P2 is not fulfilled. Other variants of MVC, e.g., Model-View-View-ModelFootnote 2 (MVVM), offer the same features as MVC with respect to the properties P1 and P3. In addition, however, MVVM defines the abstraction Command for the event exchanges between ViewModel and View but without a specific notion of event. Finally, the Model-View-Presenter (MVP) variant partially supports P2 as the pattern does not provide an abstraction representing input data availability but rather it provides an abstraction –Interactor– to signal them to the Presenter abstraction.

3.2 Improving MVC for Observability: \(o\)MVC

We refactor MVC to provide for the following observability-improving behaviors according to the definition of Observability of Sect. 3.

  1. 1.

    Store captures the application state [P1];

  2. 2.

    Action represents a generic application event [P2];

  3. 3.

    View/Normal components intercept an architecture-relevant event and construct a proper Action [P2]; they also render the application state to the environment [P4];

  4. 4.

    Store propagate the Actions into the architecture [P3, P5]; it retrieves the correct Resolver and use it to obtain the Policies to be executed in response to the event described by the Action; Store executes such Policies and updates State. Store also notifies interested View/Normal components about changes in the State; all the notified View/Normal components compute a new state representation to be outputted to the rest of the application or to the external environment.

Figure 2 shows the \(o\)MVC pattern with its main abstractions and Fig. 3 shows the class diagram of the overall \(o\)MVC architecture.

Fig. 2.
figure 2

Event and data-flow in \(o\)MVC.

The baseline component in \(o\)MVC pattern is implemented through the State abstraction whereby the isolation of the application state is realized. All the components that foster the improved application observability depend on it.

State Abstraction. oMVC State, or simply State, represents the state of an architecture, hence the set of pieces of information whose maintenance over time defines completely the characteristics and the behavior of the software architecture itself, given the problem at hand.

An atomic information is a pair datum/meaning with an associated understanding or semantic interpretation [9]. A meaning of a datum can be expressed by “appending” to it a label. For instance, .

A piece of information can be decomposed in other pieces of information, thus forming a complex information hierarchy. As State groups pieces of information we propose to structure State as a set of key-value pairs in which keys represent labels and values represent either a datum or another set of key-value pairs. Such structural model grasps the complexity of a piece of information and can be easily mapped to many efficient data structures and constructs in the world of computer science and engineering (C structures, Object oriented classes, Relational tables, Dictionaries, Hash tables, etc..).

Action Abstraction. oMVC Action is a complete description of an internal or external event that is of interest for a software architecture. In other terms, an Action groups all the necessary pieces of information capable of describing completely an architecture-relevant event, so that the architecture can put in motion proper processing procedures.

It is possible to characterise an architecture-relevant event by means of three distinct objects: the identifier and the category of the event in addition to some data that convey the content of the event itself. This characterisation has been proposed because of a simple reasoning concerning how human beings manage events: whenever an event occurs, we want to know what is the category of the event (e.g., good event, bad event, an event that concerns the family, etc.), what is the event (e.g., the door broke), and we want to know the details of the event (e.g., what part of the door is broken, in which way, etc.). This definition fosters an effective characterization of actions: for instance, an event “MouseClickLeft”, that might belongs to category “ItemClick”, can be referred to the a specific item, say “registeredUserField”, that is described by the third field of the Action. Such a general description of the events allow us to propose a structural model for Actions: similarly to State, as they both group pieces of information, Actions can be shaped as a set of key-value pairs in which two pairs are fixed: the pair that expresses an event identifier, and the pair that expresses an event group identifier. The specification we provide for a \(o\)MVC Action underlines the fact that two different events generate two different Actions, and that two Actions are different if they refer to two different events. Therefore, a one-to-one relationship exists between Actions and architecture-relevant events: an identifier for an event is also an identifier for an Action and an identifier for the group of an event is also an identifier for the group of an Action.

View/Normal Components Abstraction. oMVC View/Normal component is responsible for intercepting relevant internal or external events, making them available to the rest of the architecture as Actions, and producing a representation of the State of the architecture to be exposed to other components or to the external world, whenever the State changes. More precisely, a View component intercepts events coming only from the external world and produces a representation of the State of the architecture to be communicated only to the external world, while a Normal component intercepts only events internal to the architecture and produces a representation of the State of the architecture to be communicated only to other architecture’s components. This specification does not suggest a particular structural model for View/Normal components. Nonetheless, it constraints View/Normal components to construct Actions for describing events and to observe the State of the architecture for changes through a suitable subscription mechanism.

Policy Abstraction. oMVC Policy is a rule or a behavior that defines part of the business logic of a software architecture and that is put in motion after some event makes available some data of interest to the architecture. Policies can be distinguished into state Policies and side Policies. The formers implements the business logic manipulating the architecture state whereas the latter consist of a non State related data manipulation. Policies are implemented through procedures, that are triggered by the occurrence of some event. State Policies accept two arguments: the current State of the architecture, as a state Policy is asked to manipulate it, and an Action. Every state Policy returns the new State.

Resolver Abstraction. oMVC Resolver maps a given Action to the correct (state and side) Policies that should be executed upon it. The specification of Resolver does not impose any specific implementation yet it only requires that, for every Action, at most one State Policy and at most one Side Policy are returned by means of a proper Resolver. Many Resolvers can be defined, each one mapping the Actions belonging to a given group to their proper Policies.

Store Abstraction. oMVC Store coordinates the interaction between State, Actions, Resolvers, Policies and View/Normal components to make a software architecture work correctly. The coordination process can be split into three functionalities: (i) a look-up mechanism allowing components to access State for the architecture state retrieval. (ii) The Action propagation mechanism allowing the architecture state evolve upon the occurrence of an event. (iii) A subscription mechanism allowing View/Normal components to subscribe to changes in State, and so be notified whenever they happen.

The Action propagation mechanism is the mechanism that View/Normal components are forced to use to make available constructed Actions to the rest of the architecture so that proper Policies can be executed to handle the event encapsulated within the Action: whenever a View/Normal component propagates an Action using Store, the latter performs the following actions: (i) maps the Action to a proper Resolver and use the retrieved Resolver to obtain the (state and side) Policies associated with the Action; (ii) executes the Policies and changes the application state with the one produced by running the state Policy; (iii) notifies all the (subscribed) View/Normal components that have subscribed to State changes; Akin to View/Normal components, the Store abstraction specification does not suggest a particular structural model, but the chosen one should offer the above functionalities.

Logger Abstraction. oMVC Logger maintains the application log that stores the application behavior through a sequence of tuples (Action, State before manipulation, State after manipulation). The Logger is activated whenever a View/Normal component propagates an Action, as the Action propagation mechanism allows for the manipulation of the State.

3.3 Relationship with MVC

State, Policies and Model. The MVC Model groups the state of an application and the procedures to manipulate it, while in \(o\)MVC there is a clear separation between State and Policies. The MVC pattern does not impose any restriction on the application’s state implementation, and, in general, a MVC application may have more than one Model; conversely, State is a unique and centralized shared container of key-value pairs. Moreover, while MVC prescribes that Model knows the Views that depend on it, State is not aware of other abstractions nor implements a notification mechanism (a task outsourced to Store).

Resolvers, View Components, Store and Controller. The functionalities of the MVC Controller (that intercepts and interprets application-relevant events and causes the Model to change) are expressed through various \(o\)MVC abstractions. The Action propagation mechanism, implemented by Store, has the role of enforcing the modification of the application state, similarly to the MVC Controller. Differently from MVC, Store notifies the View components about the state change and Store commands the State to change by executing Policies, that are provided by the Resolvers. Moreover, \(o\)MVC assigns to View components both the functionalities of providing a representation of the application to the external world and of intercepting events, which is not the case for MVC that assigns the former to the View and the latter to the Controller.

Logger, Actions, and Side Policies, are not mapped to any MVC abstraction.

Fig. 3.
figure 3

Class diagram of \(o\)MVC architecture.

4 Evaluating Software Quality Metrics for \(o\)MVC

To evaluate the modifications illustrated in the previous section, we conduct an empirical evaluation of the effects of the \(o\)MVC pattern with software metrics that are specifically related to the source code of the architecture and that can be automatically evaluated through static analysis.

We designed a medium-sized online game. The player is assigned the mission to make users play an online software version, with some modifications, of the board game Escape From Aliens in Outer Space [20]. In this game, players assume either the role of humans or aliens, and, in any case, find themselves on a spaceship. The humans have the mission of escaping from the spaceship by reaching certain escape or rescuing points; the aliens have the mission of devouring as many humans as possible, possibly denying them to leave the spaceship.

The architecture is based on three patterns typically used for web-services [16], namely: (1) client-server, (2) publisher-subscriber, and, subsequently, (3) \(o\)MVC. The client-server pattern is used to implement the most of the online multiplayer functionality of the game. All the requests and responses involved in the synchronous communication strategy adopted by architecture can be modleled as messages that carry an identifier of the action to be performed on the server, and that carry additional data relevant for the execution of the action. The publisher-subscriber pattern is used to implement those functionalities of the software system that require asynchronous communication between the client and the server such as, for instance, the start of a game or the display of chat messages. The \(o\)MVC pattern is used to implement the business logic of the entire architecture, both in the client and in the server.

Evaluation Objective. We compare key software metrics computed for the \(o\)MVC proof-of-concept and for an equivalent MVC architecture — metrics results are outlined in the remainder of this section.Footnote 3

Chidamber and Kemerer (CK) metrics [6].

  • LCOM (Lack of cohesion in methods) measures how tightly bound or related the internal elements of a software module are with each other.

  • NOR (Number of root classes) measures how many class hierarchies are in the program.

  • DIT (Depth of the inheritance tree) measures the length of a class hierarchy.

  • RFC (Response for a Class) measures the number of methods and constructors invoked by objects of a class.

  • CBO (Coupling between Objects) measures how many methods and instance variables of a class B the methods of a class A uses (bidirectional uses are considered only once, inheritance related connections are not considered).

  • WMC (Weighted Methods per class) measures the sum of the cyclomatic complexity of the methods in a class. More complex methods usually require more tests for reasons of decision coverage.

Class dependency (CD) metrics.

  • Cyclic (Number of cyclic class dependencies) measures, for each class c, the number of classes c directly depends on, and that in turn depend on c.

  • Dcy (Number of dependencies) measures, for each class c, the number of classes c directly depends on.

  • Dcy* (Number of transitive dependencies) measures, for each class c, the number of classes c directly or indirectly depends on.

  • Dpt (Number of dependants) measures, for each class c, the number of classes that directly depend on c.

  • Dpt* (Number of transitive dependants) measures, for each class c, the number of classes that directly or indirectly depend on c.

The software metrics described above have been computed using the IntelliJ MetricsReloaded pluginFootnote 4, and the complete metrics results are available in the context of the same Github organisation used for the \(o\)MVC proof-of-concept.

The figures in Table 1 show the values of the Chidamber and Kemerer metrics, and of the Class dependency metrics, for the server component whereas those in Table 2 show the same metrics for the client component, comparing the \(o\)MVC proof-of-concept (red bars) with its equivalent MVC architecture (blue bars).

Table 1. CK metric (left) and CD metrics (right) for the server: \(o\)MVC-based in red and MVC-based in blue (lower metric values are better).
Table 2. CK metric (left) and CD metrics (right) for the client: \(o\)MVC-based in red and MVC-based in blue (lower metric values are better).

Server Side. CK metrics have similar values for both the architectures, with a slight advantage for the MVC architecture, and with the only exception of the RFC metric, which considerably favors the MVC architecture. RFC metric, unlike the others, does directly depend on the number of classes and methods defined in an architecture, hence, since \(o\)MVC introduces many classes for implementing its abstractions, the value of the metric is reasonably expected to be higher. The Class dependency metrics assume similar values, with a slight advantage for \(o\)MVC when Dcy and Dpt are considered, that becomes relevant for Cyclic metric. This trend ensues from the isolation and the reduced inter-components dependency that \(o\)MVC promotes.

Client Side. CK metrics show varied trends: the RFC metric is higher for the \(o\)MVC-based architecture. The CBO metric favors the MVC architecture — this circumstance is explained by two reasons: (a) the propagation of different Actions by the GUI components increases the CBO value of the Action class and, (b) the client of the \(o\)MVC-based architecture introduces more Actions than the server. Conversely, the remaining metrics favor \(o\)MVC since (a) the data-flow and the introduced abstractions reduce the complexity of classes by offloading the architectural business logic to the Action propagation mechanism, and (b) the LCOM, DIT, NOC, and WMC metrics are measures of class complexity and methods relatedness. Class dependency metrics always favors the MVC-based architecture. The Side Policies and the classes used for the View component introduce many transitive dependencies that considerably influence the values of Dcy and Dpt*. The \(o\)MVC-based architecture exploits a unique View component (i.e., GUIManager), rather than different small ones, that observes the \(o\)MVC State and orchestrates many different other components. Furthermore, the \(o\)MVC client propagates Actions inside Side Policies - that is rare in the context of the server architecture. Hence, the additional degree of recursion for the Action propagation mechanism produces more transitive and cyclic dependencies, with the consequent and perceivable metric variations.

figure b

5 Threats to Validity

Lack of Quantitative Metrics. The definition of Architecture Observability in Sect. 3 is declarative. It only outlines how an architectural pattern should be designed so that the implementing applications are observable. Observability is actually obtained because certain structural criteria (enforced through the properties P1-P5) are met. However, the observability delineated in Sect. 3 cannot be evaluated numerically as there is no a quantitative metrics that allows designers to estimate the observability of an implementation and, hence, to compare different implementations with each other. Its effect can only be measured indirectly (for instance, one can refer to the standard ISO 9126 [11] for the evaluation of some software qualities that are affected by the implementation of a specific architecture).

Implementation Complexity. The conclusions and the graphs provided in the previous section point out a generalized negative impact of \(o\)MVC for the majority of the Chidamber and Kemerer metrics in contrast to only few that are improved. This is not surprising, as the use of a structured pattern, which is implemented by means of various distinct entities, commonly introduces more “structural complexity” – i.e., more classes, methods and dependencies – than an unstructured design, realizing the same functionalities. On the one hand, a software entity implementing complex functionalities can benefit from a more structured implementation (consider the cyclomatic complexity of the server). The overhead introduced by the pattern can be less than, or at least comparable with, the complexity entailed by its functionality; and the greater maintainability of the final product compensates the use of the pattern. However, on the other hand, simple software implementations (e.g., the client side in our proof-of-concept) might suffer from the use of a pattern as it might introduce more “structural complexity” than the one actually needed, hence yielding a less concise software whose modification complexity can degrade.

Proof-of-Concept Evaluation. The evaluation of the observability of \(o\)MVC has been carried out through a proof-of-concept application of limited size. A deeper analysis is needed with the aim of comparing different implementations of \(o\)MVC when it is applied to small, medium and large applications. Indeed, the evaluation of the impact of a pattern in small applications can be strongly affected by the overhead that the pattern itself introduces.

oMVC in the Zoo of Patterns. As outlined in Sect. 2, MVC has already been elaborated and extended various times and many versions of MVC were tailored to specific contexts. The use of the specific classes such as Policy and Resolver might actually resemble as an application of the State Machine pattern. However, the purpose of \(o\)MVC is different from the one of State pattern. The State pattern promotes decoupling of state-dependent functionalities from the state object itself. A state class delegates state-specific behavior to its current state object and does not care of implementing state-specific functionalities directly. \(o\)MVC exploits a State-like mechanism to manage the state of an application but it implements policies, actions and resolvers to enrich the State pattern with event-based features that can be used in many contexts for defining the transformation of the application state upon the occurrence of events.

Discovering Patterns vs. Creating New Ones. One of the core principle of designing patterns claims that patterns are discovered but not invented [5]. \(o\)MVC is designed and its structure is not elicited from the analysis of implemented solutions. However, \(o\)MVC ensues by construction from the MVC variants in the state-of-the-art. The five properties P1-P5, enlisted in Sect. 3 to define observability, are obtained by the analysis of the known patterns and their baseline tenets. \(o\)MVC, which explicitly provides abstractions that allow designers to satisfy the requirements captured by the properties, is therefore a by-construction consequence of the known solutions that improves observability.

6 Conclusions

We have re-designed the MVC software architecture pattern to be more observable, i.e., to be able to solve the problem of state management by providing abstractions that separate how the application state is structured, how it is manipulated, how it is represented to the external world, and how the previous activities are put together and made available constantly in an analysable log.

A valuable research path reflects further re-designs of patterns for a systematic comparison against \(o\)MVC over a more significant collection of diverse software architectures, and using more extensive quality assessment. Finally, some effort should be spent in defining a quantitative metrics for observability to be measured on actual implementation of \(o\)MVC and that can be automatically evaluated through static code analysisFootnote 5.