tl;dr: FubuMVC stressed concise code and conventions over writing explicit code and that turns out to be polarizing
The typical way to be successful in OSS is to promote the hell out of your work before you give up on it, but I’m under a lot less pressure after giving up on FubuMVC and I feel like blogging again. Over the next couple months I’m going to write about the technical approach we took on FubuMVC to share the things I think went well, the stuff I regret, how I wish we’d done it instead, discarded plans for 2.0, and how I’d do things differently if I’m ever stupid enough to try this again on a different platform.
Some Sample Code
So let’s say that you start a new FubuMVC project and solution from scratch (a topic for another blog post) by running:
fubu new Demo --options spark
You’ll get this (largely placeholder) code for the MVC controller part of the main home page of your new application:
namespace Demo
{
// You'd generally do *something* in this method, otherwise
// it's just some junk code to make it easier for FubuMVC
// to hang a Spark or Razor view off the "/" route
// For 2.0, we wanted to introduce an optional convention
// to use an "action less view" for the home page
public class HomeEndpoint
{
public HomeModel Index(HomeModel model)
{
return model;
}
}
}
To make things a little more clear, fubu new also generates a matching Spark view called Home.spark to render a HomeModel resource:
<viewdata model="Demo.HomeModel" /> <content:header></content:header> <content:main> Your content would go here </content:main> <content:footer></content:footer>
The code above demonstrates a built in naming convention in FubuMVC 1.0+ such that the home “/” route will point to the action “HomeEndpoint.Index()” if that class and method exists in the main application assembly.
Some additional endpoints (FubuMVC’s analogue to Controller’s in MVC frameworks) might look like the following:
public class NameInput
{
public string Name { get; set; }
}
public class Query
{
public int From { get; set; }
public int To { get; set; }
}
public class Results { }
public class MoreEndpoints
{
// GET: name/{Name}
public string get_name_Name(NameInput input)
{
return "My name is " + input.Name;
}
// POST: query/{From}/to/{To}
public Results post_query_From_to_To(Query query)
{
return new Results();
}
}
What you’re not seeing in the code above:
- No reference whatsoever to the FubuMVC.Core namespace.
- No model binding invocation
- No code to render views, write output, set HTTP headers
- No code for authentication, authorization, or validation
- No “BaseController” or “ApiController” or “CoupleMyCodeVeryTightlyToTheFrameworkGuts” base class
- No attributes
- No marker interfaces
- No custom fluent interfaces that render your application code almost completely useless outside of the context of the web framework you’re using
What you are seeing in the code above:
- Concrete classes that are suffixed with “Endpoint” or “Endpoints.” This is an out of the box naming convention in FubuMVC that marks these classes as being Action’s.
- Public methods that take in 0 or 1 inputs and return a single “resource” model (they can be void methods too).
- Route patterns are derived from the method names and properties of the input model — more on this in a later post because this one’s already too long.
One Model In, One Model Out and Automatic Content Negotiation
As a direct reaction to ASP.Net MVC, the overriding philosophy from the very beginning was to make the code we wrote be as clean and terse as possible with as little coupling from the application to the framework code as possible. We also believed very strongly in object composition in contrast to most of the frameworks of the time that required inheritance models.
To meet this goal, our core design idea from the beginning was the one model in, one model out principle. By and large, most endpoints should be built by declaring an input model of everything the action needs to perform its work and returning the resource or response model object. The framework itself would do most of the repetitive work of reading the HTTP request and writing things out to the HTTP response for you so that you can concentrate on only the responsibilities that are really different between actions.
At runtime, FubuMVC is executing content negotiation (conneg) to read the declared inputs (see the NameModel class above and how it’s used) from the HTTP request with a typical combination of model binding or deserialization, calling the action methods with the right input, and then rendering the resource (like HomeModel in the HomeEndpoint.Index() method) again with content negotiation. As of FubuMVC 1.0, rendering views are integrated into the normal content negotiation infrastructure (and that ladies and gentlemen, was a huge win for our internals). Exactly what content negotiation can read and write is largely determined by OOTB conventions. For example:
- If a method returns a string, then we write that string with the content-type of “text/plain”
- If an action method returns a resource model, we try to “attach” a view that renders that very resource model type
- In the absence of any other reader/writer policies, FubuMVC attaches Json and Xml support automatically with model binding for content-type=”application/x-www-form-urlencoded” requests
The automatic content negotiation conventions largely means that FubuMVC action methods just don’t have to be concerned about the details of how the response is going to be written out.
View resolution is done conventionally as well. The easiest, simplest thing to do is to simply make your strongly typed Spark or Razor view render the resource model type (the return type) of an action method and FubuMVC will automatically apply that view to the matching action. I definitely believe that this was an improvement over the ASP.Net MVC ViewResult mechanism and some other frameworks *cough* NancyFx *cough* adapted this idea after us.
The huge advantage of the one model in, one model out was that your action methods became very clean and completely decoupled from the framework. The pattern was specifically designed to make unit testing action methods easy, and by and large I feel like we met that goal. It’s also been possible to reuse FubuMVC endpoint code in contexts outside of a web request because there is no coupling to FubuMVC itself in most of the action methods, and I think that’s been a big win from time to time. Try to do that with Web API, ASP.Net MVC, or a Sinatra-flavored framework like NancyFx!
The downside was the times when you really did need to exert more fine grained control over HTTP requests and responses. While you could always happily take in constructor dependencies to read and write to the raw HTTP request/response, this wasn’t all that obvious.
The Russian Doll Behavioral Model
I’ve regretted the name “FubuMVC” almost from the beginning because we weren’t really a Model 2 MVC framework. Our “Action” methods just perform some work within an HTTP request, but don’t really act as logical “Controller’s.” It was also perfectly possible to build endpoints without Action methods and other endpoints that used multiple Action methods.
The core of FubuMVC’s runtime was the Russian Doll “Behavior” model I described way back in 2011 — in which action methods are called inside of a pre-built Behavior in the middle of a chain. For example, our HomeEndpoint.Index() action above has a chain of nested behaviors in real life something like:
- AuthenticationBehavior
- InputBehavior — does content negotiation on the request to build the HomeModel input
- ActionCall –> HomeEndpoint.Index() — executes the action method with the input read by conneg and stores the output resource for later
- OutputBehavior — does conneg on the resource and “accepts” header to write the HTTP response accordingly
FubuMVC heavily uses additional Behavior’s for cross cutting concerns like authorization, validation, caching, and instrumentation that can be added into a chain of behavior to compose an HTTP pipeline. Every web framework worth its salt has some kind of model like this, but FubuMVC took it farther by standardizing on a single abstraction for behaviors (everything is just a behavior) and exposing a model that allowed you to customize the chain of behaviors by either convention or explicitly on a single endpoint/route.
In my opinion, the Behavior model gave FubuMVC far more modularity, extensibility, and composability than our peers. I would go so far as to say that this concept has been validated by the sheer number of other frameworks like Microsoft’s WebAPI that have adopted some form of this pattern.
Clean, terse “magical” code versus explicit code
The downside to the behavior model, and especially FubuMVC’s conventional construction of the nested Behaviors is the “magical” aspect. Because the framework itself is doing so much more work for you, there isn’t a blob of explicit code in one place that tells a developer everything that’s happening in an HTTP request. In retrospect, even though I personally wanna write the tightest, most concise code possible and avoid repetitive code, other developers are much happier writing and reading code that’s much more explicit — even when that requires them to write much more repetitive code. It turns out that repetitive code ceremony is not a bad thing to a large number of developers.
Other developers hated the way that FubuMVC doesn’t really do much to lead you to what to do next or make the framework capabilities discoverable because so much of the code was meant to be driven by FubuMVC conventions based on what your code looked like rather than you writing explicit code against FubuMVC API’s with Intellisense there to guide you along the way. And yes, I’m fully cognizant that I just make an argument in favor of a Sinatra style fluent interface like NancyFx’s. I know full well that many developers considered NancyFx much easier to learn than FubuMVC because of Nancy’s better discoverability.
We did offset the “magical” problem with diagnostics that I’ll discuss at a later time, but I think that the “magical” aspect of FubuMVC scared a lot of potential users away in retrospect. If I had it to do over again, I think I would have pushed to standardize and describe our built in conventions much earlier than we did — but that’s another blog post altogether.
What I wanted to do in FubuMVC 2.0
I had no intention of adopting a programming model more like Sinatra or Web API where you write more explicit code. My feeling is that there is room in the world for more than one basic approach, so for 2.0, I wanted to double down on the “one model in, one model out” approach by extending more conventions for finer grained control over the HTTP request & response without losing the benefits. Things like:
- More built in model binding conventions to attach Cookie values, system clock values, IPrincipal’s, and whatever else I could think of into the OOTB model binding.
- Built in conventions for writing header, response codes, and cookie values from resource model values to maintain the one model in, one model out motif while still allowing for more powerful HTTP API capabilities
- We did change the content negotiation defaults to make views “additive” to Json/Xml endpoints, so that any endpoint that renders a view for accept=”text/html” can also return Json or Xml by default
- We made it a little easier to replace the built in Json/Xml serialization
- We did streamline the content negotiation internals to make customization smoother
- Add new built in conventions to attach custom content negotiation readers and writers to the appropriate input and resource types