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

I'm sure this idea isn't new, but basically, the view is declarative and has place-holders for "data providers". Then the route (aka controller) provides concrete values and/or async functions (aka poor-mans-promises). The view system them executes all the async data providers in parallel and automatically merges the results.

Is this a good idea? Have you tried this? Is there something wrong with this technique?

@polotek
Copy link

polotek commented Dec 5, 2011

So the idea is you have a render method that may or may not be async. But it follows the node callback convention. Is that it? Yeah I think everyone is doing something very similar to this in spirit. But this is almost certainly the 90% api. Not sure about the templating example. But I've come to the realization that I hate all templating languages.

:Marco

@tj
Copy link

tj commented Dec 5, 2011

haha yeah im kinda with polotek, each template engine kinda covers at least one aspect really well, and then introduces new pains, so they all kinda suck in different ways

@creationix
Copy link
Author

Basically I'm taking something like step or the other async libraries and baking it into the view which is often the source of the data requirements for this kind of app.

In a traditional railsesque app, this should handle the top level of asyncness.

Also there is the added benefit that the view can do intelligent things like flush the template piecemeal as data become available. All without the coder having to orchestrate this by hand.

@polotek
Copy link

polotek commented Dec 5, 2011

Ah, actually I missed the query bit. These are the data providers you're talking about? And these return promise-y things so you don't need to add more callbacks? That seems reasonable. But there's a different idea that I'm more familiar with except it's not async. I'll work up another gist.

@creationix
Copy link
Author

@polotek, right, query is just a helper and can be found https://github.com/c9/nog/blob/master/server.js It just returns a new async function that takes a normal node callback.

@creationix
Copy link
Author

The thing I'm trying to avoid is having to first manually do the control-flow up front and then handle fully rendered results to the view. Inverting the control should both simplify the logic by being more declarative and decrease latency because data queries automatically run as parallel as possible. Even the string concats for the template are done piecemeal.

Also, the code to make this possible isn't hard at all. Here is the full implementation for my simple "corn" language, but the concept should work for any template language. https://github.com/creationix/corn/blob/master/corn.js

@polotek
Copy link

polotek commented Dec 5, 2011

This is a rushed example, so probably has lots of holes. https://gist.github.com/1434862

There are a few things going on here. First, it's more centered on domain objects that hold various data. You would load the appropriate domain object and call the render method. The render method takes a template name and the template is executed in the context of the object. That's why the title is no longer needed in the data object. You just reference this.title. The data object is now for additional data that is needed to render the template. And you access those via the data object that is added to the scope. The more I learn about rails, I realize it isn't too different from that. Only rails has the disadvantage of the rubyism of adding random things to the scope, and it's not clear where they came from.

I have another example that goes even further. But probably won't get to it for a bit. I'm not sure I'm helping here. It seems to me the major issue is loading async data in a sensible fashion before rendering your template. That's the big question. And there's 2 basic scenarios.

  • You have a main template and some partial templates get rendered first, then stitched together into the final response. You generally need all your data up front for this. So you may make multiple async calls to get all of it. Then call your top level render method.
  • You have logic in your main template that may conditionally render partial templates. Those templates may require additional data. You don't want to load everything all the time, so this partial rendering needs to also support an async api.

@tj
Copy link

tj commented Dec 5, 2011

you might want to check out http://akdubya.github.com/dustjs/ or some of the others, quite a few rocking similar concepts

@creationix
Copy link
Author

@visionmedia, thanks. That's a lot of where I was going.

@polotek. Great example. This is the kind of feedback I was looking for. I like the concept of orienting everything around the domain objects. In you're example the sub-views look like properties of the main view. My sample code is like your second scenario. The render helper isn't part of the template language, but yet another data provider. Since my data providers allow arguments, general purpose ones like this can be made and implement anything even if it does I/O (like a sub view). If you have the time I'd like to see the next idea.

@gould
Copy link

gould commented Dec 6, 2011

The general principle seems fine, the whole inversion of control is very node-like, but whats the plan for inline javascript? That and nested templating ( includes) are what Ive found to be the deal breakers for node templating.

@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