Noir's definition of views causes confusion with traditional MVC views

2,561 views
Skip to first unread message

Radford Smith

unread,
Nov 26, 2011, 10:56:07 PM11/26/11
to clj-noir
This was originally posted as a GitHub issue: https://github.com/ibdknox/noir/issues/49
---
If we look at things from the MVC perspective, Noir's views are
actually controllers and views coupled together, a mixture of domain
and presentation logic. In contrast to other frameworks in the same
space, such as Sinatra, a view is strictly presentation code. Noir
seems to be the outlier here.

I'm not criticizing Noir's decision to couple the traditional MVC
controller and view logic. This works well for small sites, which
seems to be Noir's primary use case right now. The problem is the
simply the name. If your site grows bigger, you'll probably move
toward the MVC architecture, and you'll have to juggle the concept of
a Noir view and and MVC view during the transition. Any reference to a
view in the Noir documentation is a mental switch from what most
people already know as a view.

This raises a question: what is Noir's intended place in the world of
web frameworks? Is it like Sinatra, where you build a small site and
eventually grow out of it, or is it something you can continue to
build on top of as your site gets more complex? As the author has
mentioned in the discussion group, decoupling Noir's components is
trivial. I think large sites and frameworks could use Noir as another
layer in the onion (as someone in #clojure IRC put it), similar to
Ring's use case.

We could make this happen and still support smaller sites. The
controller and view can still be coupled by default, but these
functions should not be called views. I haven't thought about a better
name too much (actions? pages, as in defpage?), but I wanted to raise
the issue, as I think it could confuse new users, especially those
coming from traditional MVC frameworks.

Here's an example: if you prefer to put your controller logic in
separate files, you're going to get something like this in your
server.clj as of Noir 1.2.1:

(server/load-views "src/my-app/controllers/")

If you're not familiar with Noir, this is nonsense.

Chris Granger

unread,
Nov 26, 2011, 11:05:26 PM11/26/11
to clj-...@googlegroups.com
Hah, I just finished my response... perfect timing :D

This is a pretty hefty subject so I'll break my answer into three parts: a bit of history, my opinion, and then why my opinion doesn't matter :)

tldr - Noir is meant to build websites of any size. While I provide some guidance on a starting point, how you choose to write sites in Noir is largely up to you.

I've been building sites professionally for just shy of 8 years and I was around for the beginning of the "web MVC" movement (I actually wrote one of those frameworks too :D). One thing most people who start out on the web don't know though, is that what we call MVC is actually something quite different than the original concept. In the early days of web MVC, controllers often took on the brunt of the business logic, while models were mostly data-access oriented - essentially nothing more than representations of the tables in the database. Controllers then asked the models for information, did whatever was necessary and passed objects to the views so they could render themselves. If you look up MVC on wikipedia, though, you'll find a different description: views ask the models directly for information and controllers exist solely to handle input and trigger events. If you apply that architecture to a web server, it would be something like this: views would ask the models directly for processed data and render HTML from it. The controller would be responsible for handling the HTTP request, as that's really the only trigger/input for a server-based site (there are a couple others, but they don't change the overall point). When you think about it though, almost everything about an HTTP request is already handled for you in most modern web frameworks. All you really need to do is declare what should happen for specific requests. What this means is that a properly factored controller is incredibly tiny, essentially it's nothing more than a function triggered per request that sometimes has an extra two or three lines to handle a little branching or setting session vars. After building these out for several years, I asked myself what's the value of this separation? The web framework is already essentially acting as the controller.

By the true definition of MVC, Noir *doesn't* mix domain logic and presentation. Domain logic should (regardless of what you're using) reside in the models, and the controller should simply pass around whatever bits come from HTTP. Throughout all the websites I've built I've never needed a heavy controller, and I've found over time that it was significantly cleaner to just phase them out of my code. Why do I need a middleman that offers me nothing other than another layer of indirection? Truth was, I didn't - not for huge websites or tiny ones. Instead of trying to follow the strict definitions of models, views, and controllers that web MVC tried to create, I've found it much better to organize my code into pieces that more closely map with the way I think about the app (which, thanks to frameworks, has little to do with http). Even for the sites that I've built that were in the 100's of thousands of lines, controllers didn't really add any value. And that's why they don't show up in Noir.

This brings me to my next point, Noir isn't trying to be anything we've seen before. As it stands, it looks a fair bit like Sinatra, though I actually hadn't thought about that til someone mentioned it on Twitter when I first released. I didn't intentionally mimic anyone, instead I focused on making something that fits well with all of the experiences I've had building sites from scratch. Clojure itself is a very different context than Ruby, Python, or whatever other language people are coming from and I think its better to use terms that fit conceptually than to try to avoid contextual overlap. The use of "view" in this case, is very conceptually fitting - when someone accesses a url, we notify the models and return a view of the data (whether it be HTML, JSON, Clojure or some other transport format). Models then handle data-access (which boils down more or less to state) and business logic. Very simple, very easy, and incredibly flexible.

Now for the most important part: it doesn't matter what I think. Noir is written in such a way that you can organize your site however you want. With 1.2.2-SNAPSHOT you can define a huge list of route strings in some file, use defpage with those in a "controller" and then use whatever templating you want. Don't like that either? Try something else then :) I give people a starting point, based on a decent amount of experience, but that doesn't mean it's the best way or the "right" way. It's just a starting point, if you grow out of it and find something better, Noir should be able to handle it. If it can't, then you should let me know :)

Lastly, you bring up the idea of using Noir as a base to build an even higher level framework. My question is what would such a thing look like? I'm not sure you can go much higher level and still maintain the generality necessary to build whatever website you come up with. Certainly you can create some nice abstractions over *parts* of the web. For example, you can use frameworks that abstract creating RESTFUL services or XML-RPC (like ring-finger or necessary-evil). In the end, though, you often need something more than just that. The nice thing is that Noir is written in such a way that you can use those, while still having the flexibility to do things that may not fit cleanly into such solutions. That being said, if you have ideas on how to make Noir better, or thoughts on how the abstraction could be raised even more, I'm definitely all ears! I want Noir to fit the needs of the Clojure web community. But right now, I actually think it's pretty close to the sweet spot between simple, easy, and flexible.

Cheers,
Chris.
Message has been deleted

Radford Smith

unread,
Nov 27, 2011, 10:39:03 AM11/27/11
to clj-noir

> Now for the most important part: it doesn't matter what I think. Noir is> written in such a way that you can organize your site however you want.> With 1.2.2-SNAPSHOT you can define a huge list of route strings in some> file, use defpage with those in a "controller" and then use whatever> templating you want.
Perhaps traditional controllers are not necessary, but it seems like
if you want to share some functionality across multiple pages, you end
up with a lot of duplication. For example, when you're using the same
layout for a number of pages:
(defpage "/photos" {}  (common/main-layout (...)))
(defpage "/photos/:id" {id :id}  (common/main-layout (...)))
(defpage "/photos/:id/edit" {id :id}  (common/main-layout (...)))
I first thought you'd need a controller to get rid of this
duplication, but really you just need to be able to wrap around a
group of pages:
(add-wrapper ["/photos" "/photos/:id" "/photos/:id/edit"] {} [resp] 
(if (str? resp)    (common/main-layout resp)    resp))
Thoughts?

Radford Smith

unread,
Nov 27, 2011, 10:41:23 AM11/27/11
to clj-noir
Looks like the formatting in my post got messed up. Here's a Gist with
the code:

https://gist.github.com/1397711

Chris Granger

unread,
Nov 27, 2011, 1:20:57 PM11/27/11
to clj-...@googlegroups.com
There are a couple ways that could be handled, the one that is the closest to what you have here is using middleware. It would actually look almost exactly like what you've shown, you'd just use clout directly to match the urls :) Another solution is to write a very simple macro that better describes what you're doing:

https://gist.github.com/1397925

While I don't really suggest doing the second one, the first one allows you to do more for those pages later (if you need to), is very obvious and easy to understand, and can live right next to where your views are. More importantly, it makes your code read more like what it is really representing.

Either are viable solutions and should be relatively simple to implement :)

Cheers,
Chris.

faenvie

unread,
Nov 28, 2011, 5:31:57 AM11/28/11
to clj-noir
> I first thought you'd need a controller to get rid of this
> duplication, but really you just need to be able to wrap around a

In an article on using Continuations and Monads for building a Web
Framework Jim duey writes:

" ... a web app isn't just a web app. If you step back a little,
you'll see
that a web app is actually a finite state machine. At each state of
execution,
it accepts new input and returns a result. If you renamed the
functions, this
library would be one way to define and execute state machines. This is
now
the third way I've come across to represent FSM's in Clojure in a
concise way."

is a dedicated FSM (plugable) perspective or anti-pattern ?

(in my experience, use of FSM for implementing simple web-apps
complicated things ... i am not experienced in implementing
big webapps though)

faenvie

unread,
Nov 29, 2011, 2:28:17 AM11/29/11
to clj-noir
hmm ... what i wanted to note is:

noir is at a high level, but still duplication exists and rates of
reuse could probably be increased.

to simply 'use a FSM' seams to complect things at some point.

it may be, that the integrative aspects of monads can help to realize
'is a FSM'. to have a ControllerAndView would not necessarily
constrain reusability then.

faenvie

unread,
Dec 1, 2011, 5:53:03 AM12/1/11
to clj-noir
it's still hard to imagine reuse across web-apps ...

-> copy and transform like a painter who looks at
works of other painters and gets inspired ?
(recognizable patters must exist, the simpler the better)

you could say to your web-app: 'get inspired by
the other web-app' or 'sync with recent changes'
-> supervised learning process.

if unsupervised, it would be a process similar to
reproduction of life. but random output is probably
not something a programmer wants.

(sorry for the digressiveness)

Reply all
Reply to author
Forward
0 new messages