Trinity – an MVC experiment
If you look at this blog archive, you should see that I got interested in the MVC issue a couple of months ago. Generally speaking, I discovered that both me, and most of PHP programmers have actually never seen this architectural pattern, because web framework developers tend to call MVC something completely different. Yes, it originates from MVC, but the key concepts are different, and what is more – these derivatives do have their own names. I started wondering then, how a true MVC web application would look like, and what their pros and cons in the web environment are. Of course, I needed a playground to test these concepts and I came into an idea to create a sort of experimental framework that could help me to find the answer. This is how Trinity was born in late May 2010.
Historical background
The key principle of design and architectural patterns is to provide a strict dictionary of concepts and solutions for various OOP problems. The dictionary gives them unique names and a standarized way of description, so that we can easily get to know, what we should expect from a concrete solution, how to implement it and what the pros and cons are. However, in case of MVC, I felt that someone tried to cheat me, as I saw absolutely no difference between writing web applications with a general-purpose framework, and with my own code base, except that database calls are called models, templates - views and the rest - controllers.
In September 2009, I had to take a closer look at Joomla CMS (never touch it, really
). For the first time, I was very disappointed with the code quality, but by accident, I found that it contains an MVC implementation. "Huh, I bet it is as crappy, as the rest of the code", I thought, but then I started reading the developer manual and suddenly I realized that I came into a model implementation of MVC, and those ones which I had already knew have nothing to do with this pattern. Of course, the entire concept was so crazy that I could not believe it myself for the first time: what? Framework developers who are usually very talented programmers, had not checked the pattern description before implementing it? They had not done any research in this area? Complete nonsense, so no doubt I began looking for some information in order to see who's right once and for all.
Unfortunately, my suspictions were right. MVC appeared in 1979 as a GUI architecture for Smalltalk applications, and it is the common ancestor for the whole family of architectural patterns that originate from the original design. They have their own names, they have their own professional descriptions. Of course, web environment is a bit different from desktop GUI structure, but the modifications introduced by web frameworks were still way too agressive. It turned out that probably the first web framework that implemented one of MVC derivatives and called it MVC was Ruby On Rails and the rest of the world simply copied the solution without thinking whether it is actually valid or not. The only notable exception was Django for Python, and they even explain their statement in the official FAQ. Anyway, the architectural pattern implemented by most of the frameworks is not MVC, but "Model-View-Presenter with Passive View", modified for web environment. For those of you who are interested in this topic, I present some extra reads:
- Trygve Reenskaug: MVC, XEROX PARC 1978-79 (the original MVC definition from 1979)
- Mike Potel: MVP: Model-View-Presenter - The Taligent Programming Model for C++ and Java (the original MVP definition from 1996)
- Martin Fowler - GUI Architectures
- Martin Fowler - Passive View
- Frank Buschmann, Kevlin Henney, Douglas C. Schmidt, Pattern-oriented software architecture: On patterns and pattern languages Volume 5, pages 178-179, ISBN 978-0471486480.
As a result, I was very interested in how the real MVC would behave in a web environment and is it actually useful in this sort of applications. I tested it in the Joomla project and the results were quite promising. My next thought was "I need a sort of framework, or something like that to test it more deeply, free from the Joomla quirks". During the winter and spring 2010, I made some paper drafts on how it would work and what the key concepts are. I called the project "Trinity" from the three parts of MVC triad.
I wrote the first lines of code for Trinity during the first PHPCon Poland which took place between May 21 and 23, 2010 in Świętokrzyskie Mountains. Since the beginning I knew I don't want to reinvent everything from scratch, but rather use third party projects, where it was possible. Trinity was able to run a "Hello World" example two weeks later, after a hard session of debugging and putting everything together. For the next months, I tested Trinity on various occasions and improved the implementation by testing more and more ideas. Because this was an experiment and I was exploring several different ways of solving various problems, some parts of the code must have been rewritten from scratch. The last big revision took place over two to three weeks ago, when I rewrote half of the framework in three days, attempting to replace the previous control flow due to the problems with adding a navigation manager. The progress could be tracked on the Polish version of this blog, where I was constantly publishing the experiment results from implementing various framework stuff. In English, this is the first bigger publication about Trinity.
Trinity goals
Because Trinity is an experiment, I wanted to learn as much as possible about how frameworks work internally, how they deal with different architectural problems, and how they integrate with third party components, so I have given an outline of the following design goals:
- Use the newest PHP application design and write standards: as a reference point, I chose Symfony 2 and PSR-0 class naming standard proposal.
- Use third party components, when appropriate: I don't think that I could write a better ORM library in two months than Doctrine guys, so if there is a good, general-purpose implementation which follows the recommended standards, I choose it.
- Interoperability: the framework should be dependent by definition on third party components, so implementing new ones should be natural.
- Layer structure: the framework functionality should be grouped in layers that extend the lower-level layers with new features.
- Test as many modern design concepts, as possible: dependency injection, event-driven programming, MVC, design-by-contract programming.
- Gain experience with PHP 5.3 namespaces: I've never used them before, so it was a great opportunity to learn, how they work and how to organize a namespaced code.
There was one more thing that annoys me in most of the frameworks: the hard-coded controller structure and the way the application is divided into subapplications, subpages and actions. Yes, it works for specialized projects, where most of the website structure is also hard-coded, but not so long ago I tried to build a flexible Content-Management system on Yii, where the default implementation was not suitable for how the CMS was supposed to work and it was no way to reimplement it, because it was all hard-coded. So, there was one more goal: flexible controllers, flexible application structure.
Third-party components
As a code base for the application, I chose the following third party components:
- Symfony 2 Event Dispatcher
- Symfony 2 YAML
- Symfony 2 Command-Line Interface
- Doctrine 2 DBAL/ORM
- Open Power Template 2.1
- Open Power Forms 2.1
- Open Power Classes 2.1
- Open Power Security 2.1
- SwiftMailer
Originally, I also planned to incorporate Symfony 2 Dependency Injection Container, but it turned out that the current implementation created by Sensio Labs has one quite important disadvantage for me: it forces me to choose between a standard service container, where the services are represented as class methods, and a service builder. There is no way to have some services implemented as normal methods, and the others defined in an XML file, or at least I found no hint, how to make it. Another problem is with Symfony 2 bundle mechanism, which is surprisingly a part of HttpKernel component, whereas in Trinity, the framework core is by definition independent from the environment, and the HTTP stuff is added by one of the upper layers. These are some examples of things that I had to implement on my own.
Trinity architecture
OK, so how this framework actually works? You can open its repository on Github, just please note that everything I will explain here is currently stored in lightweight branch instead of master.
As I mentioned earlier, the framework is organized into several layers, that extend the functionality of the lower-level ones:
- \Trinity\Basement - this is the core layer. It provides the dependency injection, and explains, what a module and an application is.
- \Trinity\Web - an extension that allows to build web applications. It explains, how a typical web application works, what is the control flow, and how the MVC behaves in this environment.
- \Trinity\WebUtils - this stuff is usually hard-coded in many frameworks, but here is is grouped as a separate layer with web utilities. It contains i.e. two types of controllers, template engine-independent helpers and some useful stuff for building models.
- \Trinity\Doctrine - this optional layer provides the integration with Doctrine 2.
- \Trinity\Opt - this optional layer provides the integration with Open Power Template 2 engine. All the items that depend on it, are grouped in this layer. The programmer is guarantted that there are no dependencies in other layers, so if he wants to use a different template engine, he must simply write its own template layer.
- \Trinity\Cache - implementations of various cache storages for Trinity. Unfortunately, I haven't found any general-purpose, unified API for these.
- \Trinity\Navigation - this layer deals with generating and processing navigation data.
Furthermore, the projects that use Trinity, can be divided into modules and applications. Modules are very similar to Symfony 2 bundles. Actually, if they were provided as a completely standalone component, I would use them instead of writing my own implementation. Each module has its own namespace, which must contain the \ModuleNamespace\Module class extending \Trinity\Basement\Module. The used modules must be registered manually during the system startup, similarly to Symfony 2 bundles.
Applications are a kind of modules. Actually, the only difference between them is that applications must extend \Trinity\Basement\Application class instead of referring directly to \Trinity\Basement\Module. The name of this thing is self-explainatory. Application is what we create for our customers, what we sell and what the customers run on their servers. They have an entry script in the web server directory that starts up the system. The application can be composed of several modules, and actually - many framework layers are provided as modules.
MVC implementation
Trinity attempts to keep strictly the MVC goals, where it is only possible. As I explained earlier, by MVC I do not mean the stuff that is implemented in most frameworks, but the original pattern definition, so from this point you should forget about all your experiences with other frameworks, because the terminology and the way it works is completely different.
The MVC triad consists of three parts:
- Model - a model represents a part of the domain seen from a concrete perspective. A single thing modelled in a computer system can be represented by different models showing the different perspectives and different entry points. Furthermore, the models tend to hide the internal implementations, and data structures in favour of providing a well-defined, unified access interface. In Trinity, a model can be any valid PHP class. There is no interface or class it must extend. It's a kind of agreement. If you say this class is a model, then it is a model and nothing more.
- View - views deal with the presentation details, encapsulating all the presentation-specific code within this part, including the so-called presentation logic. According to the MVC definition, views have a direct access to models and get the necessary data directly from them, using well-known interfaces. The views must extend a concrete class,
\Trinity\Basement\Viewwhich provides the design-by-contract features. - Controller - controllers are responsible for processing the input data and reacting on what the user does. In the web environment, controllers dispatch the request, perform some action related to the request processing (i.e. checking if the user can actually perform it), and finally - select some models and views that should respond to the request.
There are several significant differences from what we know from Ruby On Rails and similar frameworks:
- Model is not ORM - many frameworks tend to reduce the model layer to Object-Relational Mapper library. However, this forces us to move some of the model layer responsibilities to controllers, and the consequences are even deeper: we can't treat other-than-database sources as models! In Trinity, models can use ORM, but do not have to, and most of all, treating ORM objects as models themselves is considered as invalid, because it opens up some internal model architecture to the other layers.
- View is not a template - a similar thing is actually done in Django. By reducing views to templates, we are forced to put the presentation logic (i.e. navigation processing) in controllers which became dependent on the presentation-specific details. What is more, this prevents from implementing other types of output (i.e. PDF document generation) as views, which is against MVC rules. In Trinity, views can use a template engine, but do not have to, if they generate a completely different output type.
- Controller is not a proxy between model and view - many programmers think that in MVC, it's the controller that receives the data from models, and then pushes them to views. However, in the original MVC, views have a direct reference to models and decide themselves, what data to retrieve, and how. The only purpose of controller is to say: "OK, you view are going to use this model, and you - that one". The lack of this direct connection is introduced by one of the MVC derivatives called Passive View, but claiming that this is the way how MVC works is a mistake.
In Trinity, I decided that the most optimal use of the MVC triad is to let the views work with as many models as possible. Consider a list of elements, a typical element of a CRUD panel. Who said that you have to create a separate view for each CRUD panel you want to have? In Trinity, there is a generic list implementation, called \Trinity\Opt\View\Grid. It can work with any model that implements \Trinity\WebUtils\Model\Interfaces\Grid whose definition looks like this:
*/
interface Grid extends Message
{
/**
* Returns the column headers.
*/
public function getColumnHeaders();
/**
* Returns the items.
*/
public function getItems();
} // end Grid;
As you can see, this interface guarantees the view, that this concrete model is able to provide the column headers and the list data, and this is actually enough for the view to display it. Now all you have to do is to write a model that implements this interface and since then, a ready view is waiting for you. We can go even furhter, and create similar views for forms, or even a complete CRUD controller with ready actions. We do not need code generators to generate CRUD panels, because they are obsolete, since our actions for controllers are very short:
namespace UserManagement\Admin\Group;
use \Trinity\Web\Controller\Manager;
use \Trinity\Opt\Controller\Group\Crud as Group_Crud;
use \UserManagement\Admin\Form\Admin as Standard_Form;
class AdminGroup extends Group_Crud
{
public function initCrud(Manager $manager)
{
$manager->facade->select('admin');
return $manager->getModel('\\UserManagement\\Model\\Admin');
} // end initCrud();
public function getForm(Manager $manager, $type)
{
return new Standard_Form($manager->services->get('Opf'), $type);
} // end getForm();
} // end AdminGroup;
All we have to do is to write a model and define a form structure. The controller is waiting for us. Such approach has various advantages over scaffolding. With scaffolding, the actions and the way they work are predefined at the beginning. Now suppose that after a while, you need to modify some actions in order to add i.e. column sorting or some other new CRUD stuff. Suddenly, you are forced to modify all the controllers, because there is no single, common implementation. In Trinity, all you have to do is to update the base CRUD class, and all the panels start using the new feature immediately.
You might ask, how I deal with various CRUD needs. For example, some panels should not offer row editing or so on. It is simple. If I do not want to provide row edition, I simply do not implement \Trinity\WebUtils\Model\Interfaces\Editable. The view used by CRUD controller notices that this interface is not implemented, orders the template not to display "Edit" buttons", and the controller disables the edit action.
Controller types
In the previous parts of the posts I also complained that many frameworks provide a hard-coded controller structure. We can write actions grouped into action controllers and nothing more unless we want to rewrite half of the framework. Trinity does not force you to use this concrete paradigm. The default Web layer provides only a generic controller structure that does not introduce any kind of dividing the functionality into smaller units. It's a responsibility of the concrete controllers available in WebUtils. The framework provides not one, but two types of controllers by default, and writing new ones is very simple.
- GroupController - this controller provides the functionality similar to the other frameworks, where actions are joined into groups. The only difference is that they are called action groups, not action controllers.
- ActionController - here, every action is implemented as a separate class.
GroupController is useful for backends, with lots of CRUD panels mentioned earlier. ActionController is more suitable i.e. for blog frontend, where it's hard to say, how to join the actions into groups. Usually, we have there such actions, as PostList, PostPreview, Comment, Archive, InformationPage or something like that. It's very natural to write each of them in a completely separate action.
Of course, the problem is the controller interoperability. Usually, controller implementations provide various utility methods and helpers that simplify doing some things or provide action chaining etc. With such a flexible model, this turned out to be quite problematic, so I decided to check a completely different approach. Following the Single Responsibility Principle I said that the controller implementation should neither provide any helpers, utilities at all, nor implement any sort of chaining. Its only responsibility is to say, how to divide the controller layer into smaller units and nothing more. If the request says that the user wants to see XYZ logical unit, the controller is not allowed under any circumstances to call ABC in other way than HTTP redirect. So, how to make the actions more flexible? The answer is the Controller Manager and Bricks.
Controller manager
Controller manager is a simple utility that provides various controller-independent helpers, for instance the methods for processing views, creating models etc. It is instantiated separately and injected into the controller as a separate object, thus, we do not have to implement this functionality from scratch when we want to get a new controller.
Bricks
Bricks introduce some concepts of Hierarchical MVC architectural pattern to Trinity. Basically speaking, they are a self-contained controller units which can create models, views and order processing them. The only difference is that they are not fired internally by Trinity, using the request data, but by the upper level unit - some controller action or... other brick! Let's imagine a community website, where we have a user profile. We know that the user profile page should have some status form, a community wall and maybe some profile information. We pack each of these things as a separate brick and compose the final action of them:
class ProfileGroup extends ActionGroup
{
public function indexAction(Manager $manager)
{
// The generic controller state shared by the bricks
State $controllerState = new State;
$controllerState->id = $manager->request->id;
$controllerState->modelName = 'Application.Model.User';
// The action itself verifies the request
try
{
$statusBrick = $manager->createBrick('Application.Brick.Status', $controllerState);
$wallBrick = $manager->createBrick('Application.Brick.Wall', $controllerState);
$profileInfoBrick = $manager->createBrick('Application.Brick.ProfileInfo', $controllerState);
$statusBrick->dispatch();
$wallBrick->dispatch();
$profileInfoBrick->dispatch();
}
catch(NotFound $modelException)
{
// Use the error view to display a message
$errorView = $manager->getView('Application.View.ModelError');
$errorView->addModel('exception', $modelException);
return $errorView;
}
} // end indexAction();
} // end ProfileGroup;
This example perfectly shows the idea behind Trinity. The controller is always responsible for the request verification and nothing more. Such controller elements, as actions, are often equipped with various access checking, request data validation and so on, and this is why the concept of action chaining makes lots of logical problems. But here, the action can handle the request-specific stuff in its own way without any problems, and if it decides that everything is OK, compose the more detailed functionality from bricks.
Notice how the model problems are handled. We do not have to check if the specified user actually exists. Models do make use of exceptions. If the row is not found, NotFound report exception is thrown that can be captured and treated as a data model for the error view which will make a nice error message of it.
Application division
The last interesting and unique concept tested in Trinity is the way it splits the application into smaller units. When creating an application, we usually create two parts: frontend and backend. Sometimes, there can be more, and the relationships between them are also different and application-dependent. Various frameworks offer different ways of handling them. Note that their descriptions may be somewhat confusing, because the developers often use the same names to name different things:
- Zend Framework for a long time did not offer anything that would ease such division. If we wanted to split the application, we had to do it manually. In the last versions, subapplications have appeared, adding an application hierarchy.
- Symfony - in Symfony, splitting the application into smaller units is very easy and is done by default. The only thing we have to remember is that these smaller units are called applications and what I call application, is simply a project. The applications can share many things, including models.
- Yii Framework - provides hierarchical modules. An application is a module, framework, too, and smaller units can be created as submodules of the application module.
Various approaches have their own pros and cons. Probably the most flexible of the mentioned implementations is that from Symfony, because inspite of applications, we have plugins, too, which can add concrete functionality, like blog or comment system which are independent from the application division. On the other hand, similar trick is a bit hard to implement on Yii, because such plugins are modules, too, and between modules, there is just a hierarchy. In other words - we either use the modules to split the application into logical units visible to the user (frontend and backend) or into functionality units, like blog, comments etc.
In Trinity, I wanted to go a step further. In the existing implementations I lacked one thing: the possibility for defining the application units dynamically, i.e. using the information stored in the database. Such a feature would be very useful in CMS applications, and I discovered the need, looking at my Polish blog and an old CMS I wrote somewhere in 2006, where I make use of it: basically speaking, I have several areas in different subdomains: for blog, articles, photo gallery, backend etc. Their configuration, i.e. the used domain name, layout details, navigation title is stored in the database, and theoretically I can create new ones just by clicking a button in the control panel, selecting a title and the subdomain name. No dealing with web server configuration, PHP coding and so on. I realized that such a functionality would have to be built over the existing framework somehow, because the standard mechanisms are too static.
This how it is done in Trinity:
- The application is divided into areas which consist of two things: the metadata, that can be loaded from anywhere, and an implementation, implemented as an application module.
- The application may use different modules.
- The modules can extend different areas with new functionality, i.e. adding new panels to backend and widgets to frontend.
- There is a strictly defined mapping, which module supports which areas.
- Within a request, Trinity loads all the registered modules and the active area only. Inactive area modules are neither loaded, nor executed.
The point is that both area and module selection must be built-in into router in order to make it effective. The key component is \Trinity\Web\Area\Manager which contains the mappings between modules and areas. Furthermore, it loads the area metadata from some source (XML/YAML file by default, but ambitious programmers may store them in the database easily). Now, the execution begins. First of all, we must discover, what area the user wants to see (hint: the user knows, how the website is divided into areas). There are two possible scenarios:
- The active area is defined statically, as in Symfony: we have separate entry files for each area in the web server public directory, where it is explicitely said: this entry scripts runs this area module with these metadata.
- There is a single entry script and the area selection must be done by Trinity. The framework loads a component called area selection strategy. Its purpose is to use the request data, and lookup some kind of area registry in order to select the metadata and the area module.
The strategy discovery process can use the database to load the definitions, and in fact, the most optimal way is to implement both the strategy and the metadata loader as a single class in this scenario. Thus, the areas can be created dynamically by some form of CRUD panel, and we do not have to play with server configuration or PHP coding in order to manage them.
Conclusion
Trinity is an experiment and I do not currently think about releasing it and making a competition to Symfony 2 or Zend Framework. It is not this scale and not this league, especially when we see that I have even problems with maintaining Open Power Libs at similar level. However, Trinity got relatively many followers on Github (16 followers and three forks at the day I write these words). As for an experimental project, it is a lot. I hope the project will inspire some of you, too, to make private investigations on alternative application architectures. My point is not to prove that "MVC is better" or something like that. It's simply different, designed for different purposes. In some areas, it should do better than MVP and passive view, in others - it will do worse. My only dream is that one day the programmers will start using the correct names for the concepts they implement, because this is how design and architectural patterns should work.
I just provide the repository link once more: Trinity at Github.
January 17th, 2011 - 21:30
Do you fancy a project to test your experiments, I have a project where I want to rewrite some procedural code and replace it gradually with a MVC framework (Paid of course). The application has all the components required to get full use of your proposed architecture.