Luna

Asana's in-house framework for writing great web apps

When writing a complex, highly-responsive web application, there are all kinds of really difficult programming tasks that you end up doing over and over again for every feature you want to write. These are pains all too familiar to authors of Web2.0-style software (and interactive software in general). When we started Asana, we knew we wanted to build an application that is simultaneously very sophisticated in functionality and very fast in experience, and felt that the existing toolset just wasn't up to snuff. So we built Luna, an in-house end-to-end framework that automates the busy work of writing rich web applications to an unprecedented degree.

note: Though still accurate on Luna's motivation and capabilities, this video talks in terms "Lunascript", a custom DSL we initially implemented that compiled into JavaScript. We backed off from that in favor or a set of JS idioms/conventions on top of the Luna runtime. (The Lunascript compiler produced object code that was way slower than hand-written JS, and we've been able to make the JS syntax acceptably concise, as per below.)

Motivation

All of us on the Asana team have deep backgrounds writing rich web applications at companies like Google and Facebook. We’ve been continually frustrated by how long it takes to write software, and by a nagging feeling that in some deep sense we’ve been writing the same code over and over. Even when using the latest and greatest frameworks and disciplines, writing fast, highly interacting web applications involves a lot of accidental complexity:

First you need server code to figure out what data the browser needs. Hopefully you have an ORM layer, but you still need to carefully structure your code to minimize your backend dispatches, and you need to carefully keep that in sync with your front-end code lest you don’t fetch enough data or hurt performance by fetching too much. If it’s a Web 2.0-style app, you re-implement a ton of that server-side code in JavaScript, once for creating the page and then again as controller code for keeping it up to date and consistent. And when the user changes something, you bottle that up — typically in a custom over-the-wire format — and send it as an XHR to the server. The server has to de-serialize it into different data structures in a different language, notify the persistence store(s), figure out what other clients care about the change, and send them a notification over your Comet pipe, which is handled by yet more JavaScript controller code. Offline support? More code.

This is not a one-off task: it’s rote work that adds complexity to every feature that you build in every application. By the time you're done with all this, the important and novel parts of your application are only around 10% of your code. We wondered whether we could build a programming system in which we just wrote that part — the essential complexity — and a framework handled the other 90%.

Solution

Inspired by incremental computing, we’re building Luna as a simple way to write modern web applications. Luna feels like writing server-side Web1.0/PHP-style view code, but has powerful reactive semantics.

A Luna application specifies a data model and a function from the model to the view (user interface), annotated with handler functions from user inputs to model mutations. From this, the Luna framework produces a functioning Web 2.0 application — the client-side JavaScript, the server-side SQL, and everything in between — complete with real-time bidirectional data synchronization. There’s no need to write separate code to help the server figure out which values need to be sent to the client: the server can do this by simulating the UI. Because a Luna application specifies only how the UI should look given the current data (rather than how the UI should be updated as changes happen) it’s impossible to write a UI that loads correctly but does not stay correct as changes are made.

Example

The easiest way to get a sense of how that’s possible is to look at some code. Here’s how you’d write a simple-but-complete multi-user chat app:

// Some of this is a little pseudo-coded for clarity, but it's not far off from
// how we actually code (except that in practice we'd keep model and mutation
// code more separate from view code).

defineType('World', {
  messages: List/*<ChatMessage>*/
});

defineType('ChatMessage', {
  user: User,
  text: String
});

defineType('Session', {
  user: User,
  new_comment: String
});

function renderMessage(message) {
  return DIV([
    IMG({src: message.user().small_pic_url()}),
    DIV({'class': 'bubble'}, [
      B([message.user().name(), ': ']),
      DIV([message.text()])
    })
  ]);
}

defineApp('Chat', World, Session, function(world, browser, session) {
  function postMessage() {
    // Only handler functions can request data mutations.
    world.messages.append(ChatMessage.create({
      user: session.user(),
      text: session.new_comment()
    });
    session.new_comment.set('');
  }

  return DIV([
    DIV({'class': 'messages'}, {
      world.messages().mapR(renderMessage)  // "R" is for reactive
    })
    DIV({'class': 'new_comment'}, [
      IMG({src: session.user().small_pic_url() }),
      DIV([
        INPUT({ data: session.user().name }),  // `data` does bidi binding
        B([' (your nickname)']),
        FORM({onsubmit: postMessage}, [
          INPUT({data: session.new_comment})
        ])
      ])
    ])
  ]);
};

Conclusion

While Luna still has its fair share of warts, with a lot of exciting problems yet to be solved, we've found it to be a powerful abstraction that fundamentally changes the nature of how we write software. That difference increases the complexity and nuance of product designs that we take on. And, perhaps most importantly, Luna enables us to deliver a desktop-quality experience in a web browser, with real-time collaboration automatically baked into every feature we build.

For now, Luna will remain an in-house toolkit as we continue to develop and use it in service of building the Asana product. But if the prospect of working either in or on Luna excites you, we're hiring!