Nicholas Zakas: Scalable JavaScript Application Architecture

Uploaded by yuilibrary on 28.08.2011

>> NICHOLAS ZAKAS: I'm not sure how long ago it was, now; maybe a couple of months ago.
Whenever Velocity was, Gonzalo and I were hanging out, and he said: you know what I
really want you to do a talk on? JavaScript architecture, and how you make stuff so that
it keeps working, and doesn't break. This is where that talk came from.
I'm going to apologize up front; this one is a little bit more of theory than the talks
that I usually give. I've tried to intermix some code, but I don't have a lot of code
to look at, so to make up for that I've put in a lot of pictures. I hope you guys don't
mind that.
A little bit of background on me. As Gonzalo said, I work on the homepage. I'm also a contributor
to YUI, testing library profiler cookie I wrote, and I've done a few books, and wrote
a chapter for Steve Sauder's Even Faster websites -- all great stuff that you should go out
and buy.
Why is that funny?
What I really want to talk to you about is JavaScript application architecture. When
people think about architecture, they usually start by thinking about a whiteboard with
a bunch of boxes on it, so I thought it would be remiss if I didn't start out with that
as well. But in most cases the boxes represent things that you actually end up building,
so what I want to talk to you about is that building is actually an application framework
for your JavaScript.
I like to think of an application framework as a lot like a playground. There's a bunch
of different stuff that's going on in a playground: you have your swings, and your slide, all
kinds of different things that are somewhat related, but not necessarily related to each
other. They're related in their final purpose, right, you go there to relax and hang out,
and have fun. But the slide and the swings don't necessarily know what the other is doing.
I think that's a really good metaphor to talk about good architecture in general.
A lot of people stop at this point and say wait a minute, isn't that what JavaScript
libraries do? Like, there's a bunch of stuff out there and you just get to use it? But
JavaScript libraries are more like a toolbox. They provide a bunch of stuff for you to build
things with. You could build a playground, or maybe you could build a car, or maybe you
could build a building. Those tools are there for you to use, to continue building -- that's
not where you should stop.
What we're really talking about as we're beginning this architecture is, the JavaScript base
library is the very bottom, lowest level. Then on top of it you have the application
core, that's actually going to run the app. I'm going to get a little bit more into that.
But first I want to talk about a little thing I like to call Module Theory. And Module Theory
basically says everything is a module -- anything that's out there, anything that can possibly
be defined, can be defined as module. If we're talking about module definition, this is actually
the dictionary definition of 'module'. Some of these don't really fit. The one that I
find most interesting is number four: 'an independently operable unit which is part
of the total structure of a space vehicle.'
I think that's great. I think that's exactly what we're doing. We're building this space
age stuff, and you want to get it into a bunch of little pieces. We have the international
space station out there in orbit -- I told you there were pictures. It's really, really
interesting; it's this massive thing that's just floating around in space. How do they
get it up there, anyway? It's huge, and clearly not very aerodynamic, so if you were just
going to shoot that thing up there, it would blow up pretty quickly. What they did was
they separated it into a bunch of smaller modules, and each of the modules was built
separately, built so that it would interlock with other ones, and each module is pretty
self-contained. If you chop off any one of these, everything continues to work. So that
was a pretty interesting metaphor for how to build things. If you can take this modular
approach with something as complex as an international space station, it might just have a chance
to work on your web application. Not sure, but we'll see.
So this is the question: how does it apply to web applications? I like to think of this
definition of a web application module as very similar: we're talking about an independent
unit of functionality on the page. Sometimes that's easy to locate, and sometimes it's
not, so I'm going to start with a place where it's easy to locate, and that's on My Yahoo!
How many My Yahoo! users do we have? Nice, excellent. So you're probably used to this
concept of having all these little modules on your page that are each doing something
different. They're pretty easy to identify because they have boxes around them. There
they are. Very easy. They're all doing different things.
But you can actually go one step further and take a look at the header and say: you know
what? I bet that I could break that down into a bunch of smaller modules as well. Maybe
it looks something like this. I don't know, it could. Part of the debate is exactly what
part is an independent module, and what parts are interrelated, so I don't want to get too
deep into why I think this is how it should be separated. But as you're going through
and thinking about various web applications that you're working on or use, try and think:
OK, what exactly is the functionality of this piece of the page? And how much of the rest
of the page is related?
Web application modules in general consist of HTML, CSS, and JavaScript, and all within
one part of the page. When you think of it that way, if you're just working on the HTML
and the CSS and the JavaScript of that small part of the page, massive problems become
much simpler.
One of the key concepts is that any of these modules should be able to live on its own.
So if I take 'weather' and I take it out, I should be able to just put it onto an empty
page and have it work. The reason why that should work is because it's all self-contained;
there's nothing about weather that knows that it's on My Yahoo!, it doesn't matter. It should
just continue to work so long as it has its HTML, its CSS, and its JavaScript. That's
one of the goals of creating scalable JavaScript application architecture.
Loose coupling is really, really important in this. You want to make sure there aren't
artificial dependencies between your modules, which tends to happen a lot in web development.
You really have to keep an eye out for them, because tight coupling is your enemy. Loose
coupling is your friend. In order to enforce this, you have each module with its own sandbox.
I'm not talking about sandbox in the strict security sense that we usually think of it,
so it's more of a convention of a sandbox, where people just say OK, I know that I'm
supposed to stay within the bounds. Kind of like a little kid -- there's nothing preventing
the little kid from getting up out of the sandbox and leaving, except for the fear of
getting caught by the parent that's standing by.
Again, take a look at the architecture that we have thus far. The sandbox actually sits
on top of the application core, because you don't want modules interacting directly with
the core. So the modules kind of exist on the periphery. What they interface with is
the sandbox, and that is their view into the world.
These are the four basic parts of the architecture I'm going to talk about. Let's start out with
the modules. Modules have limited knowledge of what's going on on the page. Each module
knows about the sandbox, but it doesn't know about any of the other modules that are on
the page, and that's really important.
This is just some sample code presuming how you might register a module. I have nothing
that actually runs behind this, so if anybody's going to ask me for sample code afterwards,
it doesn't exist, it's just on this slide. But the basic idea is that you want to register
your module somehow, with the core, to let it know that it's there. As part of that registration,
you make available this sandbox object. In this case, I'm saying I want to pass in a
function, and then the sandbox option is passed in as an argument. Then from that an object
can be created, using that sandbox. When it's returned that can be used by the core, in
order to run that module.
So which parts actually know about the web application being built out of that whole
stack? None of them do. Each of those parts is like a puzzle piece, and it really doesn't
matter if each puzzle piece knows the whole picture -- it just has to do its job, and
that's all. Exactly what is a module's job? It depends on the module, of course. A weather
module's job is to tell you about the weather. Stock modules tell you about the stock market.
So each module's job, really, is to create a meaningful user experience within its boundaries.
That's what it's supposed to do. Then the web application is created when everything
is doing its job. The entirety of My Yahoo! is created when all the modules on its page
are doing their job. And the entirety of the homepage is created when all of its modules
are doing their job. In this case, Hello Kitty is created when the puzzle is put together.
Really, there's no laughs for that at all? OK.
This doesn't mean that modules can do whatever they want in order to do their job. Definitely
not. I like to think of modules as kids, because you need to give them guidelines so that they
don't get into trouble. Since most web developers are like kids, that goes double for them.
Some of the rules that modules need to abide by are actually similar to little kids' rules.
First: hands to yourself. Only call your own methods, or those on the sandbox, and you
shouldn't be touching anything you don't have permission to touch. Don't access DOM elements
outside of your box. Same thing. You have no need to do that -- everything you need
should be contained within one box, and you just touch everything that's in there. Don't
access non-native global objects. Hopefully there's none floating around, but you should
definitely not be worrying about global objects, because creates a really strict dependency
on where that module can live - you have to make sure that all those global objects are
always available wherever the module can live, and that's really fragile.
Next: ask, don't take. If you need anything that you can't get yourself, you need to ask
for it. That's the sandbox's job. Its job is to stand there and wait for you to say
hey, you know what I really need? I need an ice cream. And the sandbox will say, well,
let me see if you actually have permission to have this ice cream or not. If it's right
before dinner, then you probably don't.
Don't let your toys lying around. So that's: don't create global objects. You're just polluting
it for everyone else, and that increases the likelihood that you're going create some fragile
structures. And don't talk to strangers. You shouldn't be interfacing with any other modules
on the page. You don't know them, they don't know you, regardless of how much candy they
offer, just stay away.
Modules really need to stay within their own sandboxes. Sometimes as a module developer
you'll start to feel cramped, and that you don't have enough access, but really, the
level of access that you have is enough for you to do your job, and it's also enough to
ensure the overall stability of the application.
Next part is the sandbox itself. There it is, sitting there in the middle. The sandbox's
job is to ensure a consistent interface to the modules. That's the only thing that the
modules know about, so that really has to stay the same. You don't have a lot of flexibility.
The sandbox also acts like a security guard, as I said earlier. It knows what the modules
are allowed to access and what they're not. So there's a bit of marshalling going on there.
Here's a very simple example. Let's say in the admit method you want to check if you
can haz Cheezburger. You need to check that first before you can try to do something.
So call it on sandbox, and if you can, you say thank you. Told you, really light on code
today. So the sandbox's jobs. I would say consistency: the sandbox has to be very dependable.
Security. Determine which parts of a framework a module can actually access. Communication,
which turns out to be very important. Translating requests that the modules make into core actions.
I know that the module wants to do this -- what does that mean at the core level? It's very
important to take the time to design the correct sandbox interface, because this just has to
stay the same. It has to remain consistent. You can add new functionality, but you can't
remove it, and you can't change existing functionality. This is actually where you should spend the
most amount of architecture design time, in figuring out this interface. Eventually you're
going to have a ton of modules, and you don't want to have to go back and touch a ton of
modules when things change.
The next part is the application core. This is where we start to get into fun stuff. The
core manages modules -- that's its only job. What does that mean? The first thing is it
tells a module when to start and when to stop. A lot of times we fall into this habit of,
OK, I know on page load I need to do stuff. Or on DOM content ready, I need to do stuff.
And really, if you have everything on the page hooking into those events, you end up
with chaos, especially because you're not sure exactly what else is going on, or in
what order things need to happen in order for them to work properly. So it shouldn't
be up to a module to start itself -- it's up to the core to say OK, you now have permission
to start. That may be on load, it may be before, it may be after, but the core is really the
only thing that understands when it's safe for a module to start itself. On the other
end of it, it's also the only one that knows when it's safe for a module to stop itself,
whether that be on page unload, or before. So the core's main job is really managing
this module life cycle; when is it OK to start, and when is it OK to stop.
I just have some sample code to walk through about this, to show you what a possible implementation
of the core might look like. This is really, really simple: not enough error handling.
Just want to throw it out there as a prototype to try and solidify some of these things.
First thing I did was have the register method, which I showed you earlier -- I register a
module ID, and create a function. I'm just going to take those and store them for later.
When start is called, the first thing it has to do is create an instance -- so it calls
the creator method, and passes in a new instance of the sandbox object that's going to be the
module's view into the world. That instance is stored, and then this init method is called.
Clearly we're expecting that there's going to be an init method on the object that's
created. There has to be some sort of interface so that the core can work with it. And then
stop, we're just going to do the opposite. If there's an instance we're going to call
destroy, and we're assuming that it's there, and then just get rid of the instance all
Little bit more. It's probably useful to have 'start all' and 'stop all' methods that will
go through and start everything that's in the system, or stop everything that's in the
system. Just a very simple example of how to do that: go through, pull out each module
ID, and go ahead and start it. It's the same with stop. So on your web application, what
actually ends up happening is you create a series of lines of module registration -- registering
weather module, registering stock module, and whatever else happens to be on the page,
you end up registering them all -- then at the end, you 'start all'. 'Start all' is really
like calling 'application.init'. I'm starting everything, getting everything ready, and
the application is ready to be used.
Application core also manages communication between modules. This is the latest iPhone,
in case anybody hasn't seen it. A little bit more complex example is Twitter. But it's
a good example of why this inter-module communication is necessary. You can go through Twitter and
you can do the same sort of thing… Are people noticing themselves up on my Twitter
feed? I actually had to filter this before I came in; there were some inappropriate tweets
that were showing up. Do a little photoshop.
But you can go through Twitter and go through the exact same process of just finding modules
that are on the page. Some of them are really obvious, and some of them are not. Again,
you can kind of debate whether my breakdown is the right breakdown or not, but just conceptually,
you start to look at the page and you start to identify different parts as OK, this could
be its own module. As you go through, you start find, OK, so you can break this down
pretty logically. But the interesting thing here, as opposed to My Yahoo!, is that there's
actually interaction between these modules. You go and post a status and that ends up
showing in your timeline almost immediately. Then you start to click on the filters over
on the right, and those end up affecting the timeline too. Everything is just going back
to the server and saying OK, display some new information in the timeline. But you really
don't want these things to have any knowledge of what else is on the page, so this is where
this intermediary communication becomes really important.
If you were going to look at a traditional web application, it probably would look something
like this, where you have a bunch of different objects that represent different parts of
the page. Hopefully, at least, you've got this far. Then each of those refers to others.
So you have timeline filter that calls timeline, and then you have status poster which calls
timeline, and then you have timeline that has all this stuff. The issue here is tight
coupling. If you were to go through and remove timeline, then those other two objects break,
and they can't work anymore. It may just be because you're creating a new timeline that
does something different, or a different visualization, but how do you know how many other parts of
the code are referencing timeline, that you then need to go back and fix? And how confident
are you that you'll be able to catch all of those before you push everything into production?
It's very fragile.
So what you're really looking at is something a little bit more like this, where each module,
again, doesn't know anything about other modules, but it knows that it's doing something interesting
to the web application. Because it's interesting, it wants to let people know, hey, I'm doing
something. In this theoretical implementation of the framework, I have a notify method on
sandbox object. That basically says, OK, tell me what this information is, what's going
on, and then give me the data that's associated with it. So when the timeline filter changes
a filter, it doesn't access the timeline object at all, it just says hey look, I'm changing
the filter of the timeline. If anybody is interested in the fact that I am changing
the filter on the timeline, I'm just letting you know. The same with the status poster.
It's like hey, I'm just letting you guys know I'm posting the status.
If any of you happen to be interested in it, I suggest you do something about it. So this
is much looser coupling. Then if you look at the timeline, it just goes in and says
OK, I know a certain number of things that I'm going to be interested in knowing about
- timeline filter change, and post status. So in my make up implementation here, I have
a method called listen which allows you to set up a notification function whenever one
of those things happens.
So now nobody is referring to anybody else, but they're still communicating, and they're
communicating in such a way that I can move things, change things, and remove things,
and everything else continues to work. So, loose coupling? Yay. If I actually go back
in later and remove the timeline for any reason, my status poster and my filters never throw
JavaScript errors, because they're doing their job. They're letting the rest of the application
know that something interesting has happened. The fact that nothing on the application is
listening for that, and doing something, is besides the point. They've already fulfilled
their job, they've told people that something is happening.
Another important job of the application core is to handle errors, and to figure out what
the best course of action is. The cool thing about the core is that it pretty much knows
what's going on at any particular point in time, so it can also figure out exactly what
type of error happened, and then what the appropriate resolution for that error is.
It knows, hey, this really wasn't all that important, and an error happened, so I'm just
going to ignore it. Or, oh my God, my status didn't get posted because for some reason
I couldn't reach the server. That's really bad, I need to let people know about that.
That's the only part that really understands the context of the errors.
This is a function that basically goes through, and when you create an instance of a module,
it instruments each of the methods on that object, so that any errors that are coming
through are being trapped and then funneled into a log function. It's a little bit complicated,
but the basic algorithm is: go over the instance object that was created, look for functions,
and for each of those functions create a new function that has a try catch in it. Call
the original function, and then if any error happens it goes into the catch, and that goes
into your log.
Now, log can be anything. It could also just be some generic error handler that you have.
But the basic idea is that all of the errors end up getting funneled into the same entry
point, so that you know what to do. The modules themselves often don't need to know that an
error occurred, because it's usually their fault that the error happened. Most of the
time, errors like this should be caught before going out, but every once in a while that
doesn't happen. So it's really helpful to be able to recognize them, and definitely
log them back to the server.
I don't want to go too much into error handling because I actually did an entire talk on this
last year. If you want to learn more, this is the talk: it's called Enterprise JavaScript
Error Handling, that I gave at Ajax Experience last year. It's up on SlideShare, so you can
go and poke through that. But basically, everything that I say to do in that presentation is the
job of the application core. That's where all of your error information should go, and
that's where a lot of your errors should be handled.
So just to review the application core jobs. Manage the module life cycle, so tell it when
to start, and tell it when to stop. You don't want modules trying to figure that out on
their own. Enable the inter-module communication. It's really, really important for a loose
coupling to make sure that you can add and remove modules whenever you want, and not
break stuff. General error handling, to detect, trap, and report the errors that are in the
system. And also to be extensible, because the three things that it's doing, that I have
listed here, are actually not enough for it to be doing.
Why not? Good question. Web applications change. A lot of times, you have no idea the way that
they're going to evolve. Something that starts out very simple can grow to be something very
complex. My favorite example, of course, because I work here, is the Yahoo! homepage. It starts
out very simple, and ends up very complex. Of course, nobody was writing JavaScript was
Yahoo! first was launched, but the ideal is that you should be able to continue growing and changing your web application without
changing the underlying infrastructure. If you make it correctly, it should be able to
continue on for years and years. So you need to plan for extension right from the beginning.
In your architecture, there needs to be extension points, places you know you can add additional
functionality going forward, because you don't have all of it right now.
The really cool thing is that when you build stuff for extension it can just last forever.
I take the digital camera explosion as a really good example of that. The smart manufacturers,
when they made their digital cameras, made them so that all of the lenses for their non-digital
cameras could still work. How brilliant is that? Consistent interface. You get to upgrade
a piece of the architecture, and everything else continues to work. You don't need to
buy a whole bunch of new lenses, you don't need to buy a whole bunch of new cameras.
You can mix and match the stuff that you already have. Just absolutely brilliant architecture.
So when you look at the architecture we've had thus far, what we really need is extensions
to the application core as well.
What extensions could you possibly want? Error handling, for one. Maybe you want to have
an extension that decides how to deal with all of your errors in a consistent way. That
can be swapped out later if you decide to change your mind. Ajax communication. This
stuff changes all the time; we're definitely not doing it the same way that we were two
years ago. New module capability. So anything you think that a module should be able to
do, and isn't able to do, you plug it into the core, make it available through the sandbox,
and there you go: instant module upgrade. Any sort of general utilities that you want
to make available - could be for anything.
I want to focus on Ajax communication for a minute, because I think this is a really,
really important part of the extension of the application core. It's really important
because Ajax communication comes in a bunch of different flavors, and whatever flavor
you're using right now might not be what you want to use later. There's usually three parts
to be concerned about when you're dealing with Ajax communication. There's the request
format - which is basically everything that goes over the wire, whether it's a get, whether
it's a post, and what the format is - the entry point that you're hitting on the server,
and then the response format that you get back. These three things, any one of them
is likely to change at any given point in time, so you really want to have your Ajax
I'm going to start out with an Ajax XML extension, just to show you the natural progression that
I think most of the web applications have gone through in the past couple of years.
A lot of people started out with Ajax and XML, going back and forth. Let's just say,
for example, that my request format that I'm starting out with is a get request, and I'm
passing in just queries string arguments, and I'm hitting this /ajax entry point because
it's really descriptive. And the response format comes back as some XML structure, and
that has a status node in it that should be either 'OK' or 'error', and there's a data
node that has additional data in it.
So, basic implementation using low level Ajax, basic XHR object. You have a lot of code in
there. You create a new object, and you open, and then you assign your own ready state change,
and a bunch of stuff that happens in there. You check the ready state, and you check the
status code, and then you try to parse the response -- hopefully there's no parse error.
You go and you pull in the status node, and you want to make sure that the status node
indicates that it's also OK. Then you process the data that's in the data node into some
format that everything can understand, because you don't want it in XML, and then you pass
that into handle success. If any of that doesn't work, then you want to call handle failure.
So there's a lot of code written there, and this is definitely something that you don't
want to see in a lot of places in your JavaScript, because any time any of this changes, all
of that has to change. Very bad.
So you say: 'hey, JavaScript libraries really help us with that, right? It abstracts away
a lot of this stuff!' Oh hey, wait a second, that was the wrong segue. So let's go through
the rest of these pointers first. Entry point, request format, response format. OK.
'Hey, JavaScript libraries really help us with that, right? It abstracts away a lot
of the stuff that we worried about!' Yeah, OK. It looks a little bit better. This is
using YUI 3, so we call, that's cool. We've still got the entry point in there,
that's alright. We have success and failure, which is helpful because we don't have to
worry about checking for Ready State 4, and we don't have to worry about checking for
Status 200 or 304, so that helps, remove that step. But we still have some redundancy there,
because both success and failure have to call handle failure in case something goes wrong,
so if the response format is incorrect, or the server just says I did something wrong,
like oh crap, OK. So this is still a little bit messy.
We still have the entry point, we still have knowledge of the request format, and we still
have knowledge of the response format. As an added bonus, we have a reference to the
library that you've been using. So this is actually more tightly coupled than the other
one, because now you're relying on a library to be there.
So what do you actually want to do? Well, what I actually want to do is, I want for
my module code to be like this. I just want to say: I want to make a request, here's the
data that I'm sending, my names and my values, and then when this succeeds let me know, and
if it fails then let me know. In this case, the data that's coming in is already formatted
in the format you expect. Success is only called if Ready State 4 was hit, Status 200
or 304 was hit, the response format is parsed correctly, and the status node says OK. Anything
other than that, failure is called - because a module doesn't really care why something
failed. It just knows I asked for something, and I was expecting something back, and I
didn't get it. That's all it cares about.
This is very loosely coupled; you'll notice that we had none of those three things -- we're
not worrying about request format, we're not worrying about entry point, and we're not
worrying about response format. The Ajax extension encapsulates all three of these, and that
means any of these can change without forcing modules to change.
Now I'm going to change, because it's 2009, to a JSON response, but I also need to change
my entry point, because somebody said they didn't like /ajax, and that's OK, because
I can just plop in the Ajax JSON extension into my framework and everything continues
to work. It works because the modules had no idea about any of those three parts. It
still is using the exact same interface that it was before, we've just swapped stuff out
from underneath.
So the jobs of the Ajax extension hide all of those details. Modules don't need to know
any of it; it's really irrelevant to what they're trying to accomplish. Provide a common
request interface -- that basically means name value pairs that I'm going to send in,
and it doesn't need to know anything about the actual format of that. Common response
interface -- so you don't really want to take exactly what was passed back by the server
and hand it to the module, because that might change. So you want to have some good intermediary
format that you know handles all of your capabilities. And managed server failures -- again, modules
don't care why they didn't get what they were asking for, just that they didn't get it.
Let's now move on to the base library, which is the part that most people seem to be most
familiar with. Base library, the very bottom of the stack, provides basic functionality.
Pretty simple. There's a bunch of things that you can use as a base library out there. For
this architecture, it really doesn't matter all that much; just pick one and go with it.
But the problem that you need to try to avoid is that most web applications end up being
too tightly coupled to those base libraries. I think that's partly because we, as developers,
get really emotionally attached to our base libraries. If I love YUI, I love YUI, and
if you're telling me I can't use YUI, I don't like you very much. Hence this picture. I'm
here to tell you to release that emotional attachment to your base library. It's not
doing all of the job for you, and you actually have enough knowledge to do a lot of stuff
without it.
There was an interesting presentation a couple of years ago at OSCON, done by Joseph Smarr
from Plaxo. He later came to Yahoo! to give the same talk, and he had this one slide that,
at the time, I remember people getting upset about. Now I find it really, really funny,
so I thought that I would drag it back out. Basically, what he said towards the bottom,
his second big bullet point was: "to minimize your dependency on third-party library code,"
so your base libraries. "Lots of extra code comes along that you don't need" -- he was
primarily worried about performance in this case. "Libraries solve more general problems.
Use it like scaffolding." I thought that was a really great metaphor, because that's exactly
what it should do. You should use it like scaffolding to get the rest of your architecture
up and running. If anybody's interested in seeing the rest of his presentation, you can
go to check out his website; he has that up there. Very good talk.
Basically, really the only thing that needs to know about the base library is the application
core, because it needs to interface directly with that base library. The reason why that's
important is because you don't want to have that dependency on third-party code. I could
start out with my application built on Dojo and decide later on that that's not doing
it for me, and what I really want to do is switch it to YUI. Now, if the application
core is the only thing that knows about the base library, this is completely possible,
because you don't need to change anything else in the architecture. As soon as other
parts know about the base library, trying to do this swap becomes a lot more painful
and a lot more dangerous.
The jobs of the base library, which are very important. Browser normalization. We really
want any browser specific logic to be in that base library layer. The rest of the application
should never have to worry about it. General purpose utilize s- you know those, add class,
remove class, all of that fun stuff. Of course, CSS query selectors. Parsers and serializers.
Object manipulation. DOM manipulation. Ajax communication. All the stuff that makes your
life easy in general. And then provide low level extensibility; when you're choosing
your base library, you need to make sure that you can write extensions for it, as well.
So really, your architecture ends up looking like this. We have a core in the middle of
things that you rely on, and then a bunch of extension points around the outside so
that you can continue to grow your application in whatever way that you want to.
Let's talk a little bit about knowledge within the architecture, because I've thrown this
around a lot and I just want to give a summation of this to make sure that everybody understands
what parts know about what other parts. So in the total architecture, only the base library
knows which browser is being used, and it needs to know that because it needs to do
the browser normalization. So if you come across a time when you need something else
to know about the browser being used, the correct point to extend is actually the base
library. You don't want anything above that layer knowing about the browser.
Only the application core knows which base library is being used. That's really important.
These two end up being tied together. The application core's job is really to abstract
that base library away. Only the sandbox, on top of the application core, knows which
application core is being used. You actually can swap out an application core pretty easily;
you may need to recode the sandbox a little bit, but the sandbox interface always remains
the same, and it doesn't matter to the modules that are using it. The modules know nothing
except the sandbox exists. They also don't know about each other. And no part of this
architecture knows about the web application as a whole. Remember, they're just individual
pieces that are each doing their own little job.
The advantage of this is you can create multiple different applications using the same architecture.
I take it nobody's picking up on the metaphor on this one.
You use the same base, you can use the same core, you use the same sandbox. But to create
a different application, you just provide different extensions, and you provide different
modules. So you can create My Yahoo! and the Yahoo! homepage on the exact same architecture,
because the only difference is the extensions, pretty much. Everything else remains the same.
A really big bonus is that when you separate these into independent units of functionality,
you can also test them separately, because you know they don't have a whole bunch of
side effects on different things, and you know that they're not dependent on a bunch
of things in the environment that you couldn't possibly predict. So you can actually set
up unit testing a lot easier this way than you can traditionally -- you just take each
individual component, stick it into an area where you start poking it and telling it to
start and stop and do its various jobs, and then verify that that's working. Then if you
can verify that each part is doing its job, you eventually verify that the web application
is doing its job. Much like an assembly line at a pastry shop.
Basically, if you think of it like Jenga, scalable JavaScript architecture allows you
to remove any block, and the tower still stays. It doesn't matter if it's at the top, it doesn't
matter if it's at the bottom. Everything still continues to work. That means you spend less
time worrying about maintaining a bunch of code, and more time really being able to be
effective creating new features, testing things, and even creating entirely new web applications.
So that is that. Are there any questions?
The question was the naming of a module, because coming from a Python background, he thinks
of it more as a namespace. Does anyone want to make fun of him for that?
>> AUDIENCE MEMBER 1: In Ruby it's different too.
>> NICHOLAS: Yeah, so this is the general problem that I think we've run into because
a widget, to me, means something that is reusable in a number of different modules. I look at
a menu component as a widget, and a module as the weather module that uses that widget.
I think we run into this a lot in computer science, whether it's a widget, or a module,
or a component, or a part, or a stub, or an extension, or a plug in. I think whatever
makes it make the most sense to you, feel free to call it.
I've found that module has worked pretty well, and that is coming from my working on Yahoo!
and later on the homepage. Everybody was already calling it modules, so that's what we adopted,
and everyone understands. People in the room that I work with, does anybody find 'module'
confusing in the context that we use it? Besides Oliver, because that doesn't count. OK.
Interesting question. So the question was, you've talked a lot about functionality, but
what about data? Who owns it, and how does it get passed through?
For that, I guess I'll turn it around with another question. Who needs to know about
weather data besides the weather module? I don't know.
>> AUDIENCE MEMBER 2: It depends on whatever the module's are doing.
>> NICHOLAS: Right. I tend to believe that data has some specific owner on the page.
As in the weather example, I think the weather module owns the weather data for the page,
and the way that it can make it available to other places on the page is through that
inter-module communication mechanism. But then you get into interesting quandaries of:
what if someone else also thinks they own weather data, and they start passing that
around, and then where's the source of truth?
To answer, I guess, more succinctly, I think that there typically is a module that's responsible
for some specific data. If there's something that is more general, you can end up having
that as, like, application core data, and then expose it through the sandbox in some
way. Does that make sense? Potentially? Maybe after another beer it'll make more sense.
The question was, is it intentional to create a new instance of sandbox per module. Yes,
that was intention in this example. But as I said earlier, this was just a very basic,
non optimized sample example. If you find that there is a better way to do it, then
by all means do it. I thought that this might be the easier way for people to grasp the
concept. I think there are some bonuses to having an individual sandbox for each, because
you don't have to worry about anything bleeding through, or modules accidentally doing something
they weren't supposed to because some other module is actually allowed to. That makes
me feel a little bit more safe. But I think ultimately, there are patterns that you could
do either whether you have shared one that you end up passing around, or one unique one
for each module that's on the page.
That's actually right -- I am moving the problem around. I'm moving it down, and away from
where most of the code lives. Because most of the code for your application lives in
the modules, and you'd never want to touch those once they're working; you want to ensure
that they continue to work. So you're right -- that application core does end up needing
to abstract what the base library is doing, not necessarily implement. That's a bonus,
because that's what allows you to not be tied to a particular base library.
If you are comfortable saying: I am going to use this base library forever, then I say
go for it, and let stuff bleed through as much as you want. If you're not sure then
it's actually worth your time to cover up the base library functionality that you think
you need. I would venture to say interacting with the DOM, a lot of times we think of it
as really complicated, but usually you're doing relatively simple things; I'm adding
something, I'm removing something, I'm inserting something. That's more or less it. Everything
else is like add class, remove class, replace class; that stuff is useful too. So what you're
really talking about at its core is a very small amount of functionality that you're
going to provide an extra layer on, so that then you can provide it to the modules. Because
remember, Ajax is completely abstracted away, anyway, so you definitely don't have to worry
about that.
Yeah, it's definitely a concern with third-party modules, but you can provide infrastructure
around that such that it works. There are ways to do it. I probably can't get into it
tonight, but the architecture that's here really is the starting point, and you can
pretty much make most stuff work. I mean, I've worked with this on enough different
types of applications that I'm pretty convinced that no matter what the requirement is, you
can figure out a way to make it work. Sometimes you just have to be a bit more clever than
with others.
Yeah, the idea is that it always goes down through the layers and always comes back up,
so that the interface is consistent. There's not enough information in that individual
module such that it can break if stuff underneath swaps out.
>> AUDIENCE MEMBER 3: So it is true that you're playing with base code navigation core all
the time for performance reasons?
>> NICHOLAS: Absolutely.
>> AUDIENCE MEMBER 3: Because they have a lot of versions you're dealing with here.
>> NICHOLAS: Yeah. So, the question was, you're always messing around with base and core in
order to speed things up. Absolutely. That's pretty much my job at this point.
Yeah, so to sum up the question, it seems like some things are more easy to abstract
away than other things, and the example was the YUI data table, which is really complex.
Could you possibly abstract that away in such a way that you could actually swap in Dojo
or jQuery underneath? Very good question. Yes, definitely, there are some things that
are more difficult to abstract away. When there are things that are more complicated,
usually what I do is look at them and figure out: what is my use case for that thing? What
you'll usually find is that you're using only a very small percentage of the functionality
of that particular widget. You're using it in a very specific way. If you start from
that, you can usually work backwards into some sort of easy abstraction, where you say:
it's supporting everything that I need to support. So you don't actually abstract everything
- every event, even method -- you actually end up abstracting just the stuff that you
need, and that's a lot easier.
I'm not going to say that it's completely easy, because data table, I know, is very,
very complex. And it's also one of those things where, if you're comfortable saying: I will
hitch my wagon to YUI and I will never turn my back on it, then maybe that's something
that you're not going to be that concerned about. If you think that there's any possibility
in the future that you're going to change your mind, then I think it's worth putting
in a little bit more thought, and figuring out what the proper abstractions for you are.
The question was: what happens if you have six modules that are all trying to get data?
Wouldn't it be better to have them all go separately because of CPU considerations?
I think, actually, that's an argument for why it should go through the core. Because
when it goes through the core to the Ajax extension, the Ajax extension has the ability
to say hey, I already have three things queued up -- maybe it's not the best idea for me
to go off to another one. It maybe puts a stop to it until the other ones come back,
and then start again, but if you have everything going off and making their own requests, then
you have no way of stopping it, or throttling it, or filtering it in any way.
Good questions. So I guess I'm done. That's me, if you want to contact me after this,
if you're not catching me during pizza.
That's all.