Skip to content

Instantly share code, notes, and snippets.

@creationix
Created December 5, 2011 19:12
Show Gist options
  • Save creationix/1434837 to your computer and use it in GitHub Desktop.
Save creationix/1434837 to your computer and use it in GitHub Desktop.
Sanity check for async template idea
Creationix.route("GET", "/", function (req, res, params, next) {
render("frontindex", {
title: query("index", "title"),
links: query("index", "links"),
articles: loadArticles
}, function (err, html) {
if (err) return next(err);
res.writeHead(200, {
"Content-Length": Buffer.byteLength(html),
"Content-Type": "text/html; charset=utf-8"
});
res.end(html);
});
}),
<!doctype html>
<html>
<head>
<title>@title()</title>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
@render("topbar", this)
<div class="container_16">
<div class="grid_11">
<div class="section">@render("summary", articles)</div>
</div>
<div class="grid_5">
<div class="section">Sidebar</div>
</div>
</div>
@render("footer", this)
</body>
</html>
@creationix
Copy link
Author

A major feature missing from my simple implementation of this idea is nested sections. To do this, I just need to write a real parser instead of the simple regexp string splitting I'm using now. Then I can add conditionals, loops, and custom filters into the template language (think mustache or dustjs style nesting).

As a stopgap in this particular language (corn), I added the ability to send parameters to the data provider functions. With this I can do conditionals, sub templates, and loops as long as the nested html is in a separate file. Once I add a proper parser to the compiler, the body of the loops and conditionals can be nested inline without putting it in it's own file.

However, this is an implementation detail of the template language itself. What I'm asking about it the inversion of control and declarative style of defining your required data. I don't want to argue syntax or features of the template language. I can implement a proper parser and add inline sub-documents if that helps though.

As a personal matter of taste, I don't like inline code in the template language. I've done too much PHP and it's scarred me. An async, inverted-control language can still be designed that allows inline code. It will just be tricky to implement.

I'm interested in hearing about patterns of organising domain logic in an app. How would being able to declare your data dependencies change how you write controllers? How does rails-style MVC relate to node's and connect/stack's http route based interface? Basically I want to know how others organise their apps now and how would using this kind of template system change that?

@FugueNation
Copy link

Our framework has solid modeling, with validation and all so our controllers consist of rather thin logic. But there data dependencies are a bit more hierarchic, so we can't really run all our queries in parallel, for example first we need to grab the account info, and based on that grab stuff from other places.
Meaning our typical controller involves about 2-to-3 stages of async processing before we can actually render the final template. With the sample above assumes a single depth of data, which means that it looks really clean, so in our case the final parallel data collection phase would be simplified, that is helpful, but because of the other few layers of control we can't eliminate it all with just Corn. Not that I'm asking for Corn to be a do-it-all library, it already plays nice with the typical callback scheme, so it's just a matter of integration, between controller/view as unless you have a very simple system where the controller kind of disappears like it does in your examples above

On a side note I'm not really sure how query works without a callback, do you wrap the query tuple again with "[render]this" behind the scenes? Or is it some other kind of cool trick?

@creationix
Copy link
Author

I realise that I didn't show enough code in this example. The full query for loadArticles can be found at https://github.com/c9/nog/blob/master/server.js#L53-85.

query() is just a simple helper that does a db query and returns the result in a callback. If you don't provide a callback in the first call, it returns a curried version of itself. https://github.com/c9/nog/blob/master/server.js#L100-104

Load Articles does several manual db joins to gather the data. It is hand written async code and runs as much parallel as possible, but clearly I can't load the author's file without first loading the article's metadata to know who the author is.

So I think there are two kinds of async logic in rendering a view. The first is to know what page to render. This is decision logic and shouldn't be embedded in a view. Usually the url tells enough that we know right away what to load, but this might not always be the case. The second kind of logic is, once we know what to do, we need to gather and load all the required data.

This is the part I propose the view handle for us. For complicated things like "loading a listing of article ids and then loading those articles in parallel and loading the author data for each article as the author name is known" I still think hand-written code is fine. Nested async code is quite nice and efficient in small amounts.

Caching, batching, and request pooling can integrate very nicely with this model and should be for anything that involved real resources like file descriptors or 500 concurrent requests for the same page will crash your server.

Thanks everyone for the input! Keep it coming.

@polotek
Copy link

polotek commented Dec 7, 2011

Here's another gist with some really crazy ideas :) https://gist.github.com/1439922

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment