Note: this post really concerns statically typed languages, but my little bits of example code are in Ruby since that’s the MVC implementation I know best.
I like MVC-ish-ness for web applications
The MVC-ish aspect of Rails is much clearer than the ASP/PHP-ish world’s chaos, so I’m bought into MVC-ish as a better way. Separating the request processing into MVC generally gets me into the right mindset when I’m looking at individual bits of code. That said, Rails’ MVC still falls to satisfy fully my cravings for a web framework.
I don’t like the lack of type-safety
As Curt and I were discussing over in my last post, I am bothered by the lack of type-safety in MVC web frameworks. In most MVC frameworks, type-safety is ignored or broken, e.g.:
- Rails is basically not typed and Views can access the Controller’s instance variables;
- ASP.NET uses an object called ViewState to pass data from Controllers to Views, but you have to cast ViewState to the right type in the View [yes, I’m treating ASP.NET as MVC…];
- Turbinado copied ASP.NET’s ViewState and inherits its issues.
I’d like to figure out a way to maintain type-safety within my web application. In the case of dynamic languages (e.g. Ruby), I’d like “type-clarity” so that I’m nearly guaranteed of having the right type in the right place. For example [using Ruby syntax]:
The header of a web app might have a account section which behaves differently whether or not a User has been loaded into @user in the Controller. It’s easy enough to have the section behave differently based on whether @user.nil?, but I can’t guarantee that, if the @user is not nil, @user is of type User. *I* can certainly check the class of @user, but that leaves me open to errors at runtime when I _do_ change the type of @user and forget to update all of my type checks.
I want to be able to set the @user variable to a User in the Controller and have that type information enforced at compile time in the View. Given an MVC-ish system, there seems to be two ways to do this:
- Use different data types for each view and piece of a view. This would be a nightmare since you’d have to define hundreds of separate types, build them differently and then send the right type to the right view.
- Only ever create the values in question right near their usage, but this pushes Model fiddling and request params access down into the View, so doesn’t fit the web MVC style…
A Slight Modification to Rails’ MVC-ish-ness
Even if it isn’t the Rails MVC Way, it seems as though a reasonable way to maintain type safety would be to:
- Don’t use Controller to set up variables for use in Views.
- Mix more of the Controller functionality into the Models so that Models can manage creation of themselves.
- Have Views use Models directly (and have Models cache/memo-ize themselves as necessary).
- Use Controllers *only* for validation and routing.
- Have Views play directly with the Models they require.
Currently, Rails’ MVC works something like the following:
--- some_controller.rb class SomeController < ApplicationController def index @user = User.find(params[:user_id]) end end --- some_view.html.erb <= HEY! Is @user **really** a User? Log in! ---
Instead, how about giving the Model access to the params and letting it use its business rules to set itself up:
--- some_controller.rb class SomeController < ApplicationController def index end end --- some_view.html.erb <= HEY! That's almost guaranteed to work. Log in! --- user_model.rb Class User < ActiveRecord::Base def self.from_request find(params[:user_id]) <= the models is accessing the Environment... end end
This is wrong, right?
Per Terrence Parr in Enforcing Strict Model-View Separation in Template Engines [PDF]:
[…] there is widespread agreement that keeping the view as simple and free from entanglements with model and controller as possible is a worthwhile ideal to strive for.
[…] Strictly separating model and view is an extremely desirable goal because it increases flexibility, reduces maintenance costs, and allows coders and designers to work in parallel.
But I think Parr’s point is really that business logic shouldn’t appear in Views not that UserModel access shouldn’t be done in a View. Or is there some horrible breakdown that would occur if Models had access to request parameters and Controllers didn’t set up Models?
An issue that does arise with this modification is increased testing complexity when the Environment is accessible to Models. Whereas now Models take a few parameters and then pass or don’t pass the test, allowing Models to access the Environment would mean that tests would have to set up request parameters, cookies, etc. Simple, little, lovely Unit tests suddenly get larger and potentially uglier…
Type-safety + MVC ~= peanut butter & chocolate
Is type-safety a worthy goal to try to attain in MVC-ish web frameworks? If so, how is the best way of doing so? In Rails, judicious application of filters and good naming schemes can get you “type-confident”, but you’re never type-safe. Perhaps Rails has just the right blend for an MVC-ish web framework…
If MVC-ish web frameworks aren’t the way to go, what organization is better? Templating systems that only allow template markup/commands seem pretty safe, but I’d seriously miss the simplicity of mixed markup/code. ASP.NET compiles the template and then the “code behind” gets well-typed access to the XML, but I really prefer MVC to that organization.