Cool stuff in FubuMVC No. 1: Behaviors
This is the first post of the FubuMVC series mentioned in the Introduction post.
Perhaps the coolest thing about FubuMVC (and I’m not kidding when I say there’s a lot of cool stuff) is the behavior model. This fundamental concept enables almost all of the other coolness in FubuMVC. The concept is this: There is no controller. I’m not trying to be cute or clever, I mean it. When you look at some of the other MVC frameworks out there, they have a fairly strong notion of “Controller class” baked into the framework. So much so that they require a base class for you() to derive from. [() Django doesn’t require to you subclass a controller (what they call a ‘view’, confusingly) but it enables some scenarios you can’t get unless you subclass]. Main problem #1: The framework is far too invasive into my code. Stated differently: I shouldn’t have to derive from anything or import/”using” any of your code to do simple-to-medium complexity things with your framework. And if, in an average controller class “.cs” file, I need to have more “using” statements for your framework than I need for my app code, we have major problems.
To be specific, most of these frameworks are of the “Model 2” variety of MVC. It turns out that these frameworks, by nature of their fundamental design, encourage fat controllers (except, perhaps, Django, but I don’t know it well enough to speak definitively). Each has a way (some better, some worse) to get some of the logic out of the controller and move it somewhere else. But in using them, you definitely get the sense that things would be a lot easier to toss into the controller action itself and be done. The framework doesn’t “encourage” you to spread out responsibilities. A typical controller might have the following responsibilities:
- Authenticate the user
- Authorize the request
- Bind the request to one or more model or models
- Validate those models
- ! Perform the primary logic of the action !
- Respond to failure if the primary logic failed for some reason (threw an error, returned negatively, etc)
- Determine what result to send to the client (render a view, JSON, HTTP redirect, etc)
- Prepare the output model/json/redirect URL in order for the result to proceed
- Transfer control to the result (with an explicit call in many cases)
Most everyone realizes that this list is far too long for the average controller action to handle. Your method will be very long, have lots of branching, and be nearly impossible to unit test effectively. You will also be repeating a lot of code (violating the DRY principle). Problem #2: Many MVC frameworks encourage “fat” controllers.
In ASP.NET MVC, you’d move many of the earlier things to ActionFilters that run before and some of the later things into ActionResults. The other frameworks have similar ways of adding pre- and post-logic to an action which helps with SRP and DRY. While these help, the level of granularity and control over the web request pipeline generally isn’t enough to allow better separation and composition of responsibilities. Problem #3: Not enough granularity and/or compose-ability (usually, it’s an after-thought).
Seeing these problems and many others, we realized that we needed to change a few things about how we approached our web framework. We needed to have a framework that encouraged skinny controllers. When you get down to it, the only thing the controller should be doing is the “! Perform the primary logic of the action !”. We found that when we had multiple actions in a controller, that, while each action was skinny, the controller was still pretty fat. This let us eventually to determine: There should be no controller at all! All that matters is the action. Since, in C#, you can’t have a method without a class around it, we still needed a class to wrap around the action method, but that’s it. This class’s sole purpose is to provide a container for the action method. It can have fields, private methods, etc. that all support that one action method, but it should only have one action method. You can call this class a “controller” if you like, but it has very little in common with the Model 2 frameworks’ “controller” classes.
The solution we came up with involved a pipeline of small, compose-able units of functionality. Each should have its own small responsibility. Each should have the ability be able to wrap the entire pipeline to execute before, after, or around the action. When we first built FubuMVC, we called these “decorators” because they were originally designed a lot like the decorator pattern, but not exactly. They also behaved somewhat like a chain-of-responsibility pattern, but not exactly. They also behaved somewhat like “commands” in the Front Controller pattern, but not exactly. We struggled with the nomenclature and a word that captured the unique essence of what these things did. Hat tip to Steven Harman, he coined the term “Behavior,” and it stuck. More than just ActionFilters and ActionResults, behaviors can hook into and override any part of the pipeline allowing for many interesting scenarios and maximum flexibility when building complex, compositional applications (which is what we were and are still trying to do at Dovetail Software – we’re hiring, by the way).
The way behaviors work is simple. They have a very simple interface (basically one method: Invoke() and a method InvokePartial in case the behavior needs to behave differently when being invoked in a partial). Since everything in FubuMVC runs through IoC and is fully compositional in design (versus inheritance-based as in the other frameworks), a behavior takes in the next behavior in the chain and can execute it or not. By not executing it, the behavior takes on the responsibility for completing the entire request. An example of this would be an authorization behavior that needs to usurp the pipeline because a user doesn’t have the correct permissions to view this page. The behavior will then send the request down another pipeline that results in an “HTTP 403 Access Denied” view being rendered. For your convenience (and only for convenience, it’s not required), there is an abstract base class you can derive from which does some of the boring basic behavior stuff.
To learn more technical details and how-to about behaviors, you should probably read the FubuMVC Guide on the subject (yes, these guides look like Rails guides because they are a fork from Spree guides).
Conventions, the real pay-off
Up until now, you may think something along the lines of: “Meh, behaviors are cool, but they’re really just glorified before/after filters/results and don’t really change *that* much of the game.” Of course you’d be wrong, but not unreasonable – until you consider the conventional application of those behaviors to actions.
As the sub-title suggests, conventionally applying behaviors to actions is where things get really interesting and when most people have the big “Ah-hah!” moment. Again, I encourage you to read the “Advanced Behaviors” guide as it goes into a lot of the specifics. This post will just be an overview and conceptual treatment of the subject.
Every app has its own conventions whether you call them that or not. When I say “conventions” I mean anything that, when a new developer joins your team, you explain to him/her that “this is the way we do things.” For example, any method in any class in this namespace is “secure” or any method with the “[Secure]” attribute, and any method whose name ends in “Secure” are examples of conventions. FubuMVC allows you to define those conventions in code, make them first-class citizens in your app, and use them to apply behavior(s) to your actions. The best part is, you don’t have to use any of the framework’s attributes or interfaces or base classes. You can do it by names of types or methods, or by your own custom attributes or interfaces. This helps lessen the number of “using” statements required to use the framework. Our goal is to have zero-to-a-few “using” statements in your app code. When we talk about more or less “intrusive” or “unobstrusive” (look Ma, no using statements! Pure POCO!), this is what we mean.
In fact, almost everything in FubuMVC is based on your conventions. The idea is FubuMVC brings the bag of tools and you tell it where and how you want things wired up.
I realize this post was light on code and some specifics. This was intentional. I wanted this to be more a philosophical post about our approach. It is important for you to understand this as the rest of the posts in this series will make increasingly more sense as you begin to understand this and think like we do when it comes to approaching framework building (especially web framework building). If you really want specifics and haven’t checked out the “Advanced Behaviors” guide, I invite you one last time. As this series progresses, I’ll get into more specifics of just how deep the configurability and conventional approach runs in FubuMVC and a lot of this post will begin to make more sense.