Learning with teams

Editor’s note: This is a cross-post from Udacity‘s Dhruv Parthasarathy. Dhruv shares how, with the collaboration with Layer, they were able to successfully implement their classroom chatroom feature, Teams.

Despite all the advancements made to date, online education still has one glaring shortcoming — it is lonely. Learning online today is largely done on your own, at your computer, cut off from those around you. In essence, we’ve lost the human element of education that brings so much joy and richness to the experience. Here at Udacity, our goal is to create an educational experience that is better than any other, online or offline, and so long as our experience lacks this human element, we know we will continue to fall short of our goal.

Seeing this, we’ve been focused on bringing real, human relationships to the core of our product. The manifestation of these efforts is Teams: a new experience in Udacity where you join 10 other fellow students from around the world in a small, tight-knit community focused on learning. Students on teams answer each others questions, collaborate on projects, and help each other achieve their goals. Beyond that, they also get to participate in a real community with students who share similar career and learning goals. In order to create such a community, we knew that we would need to provide a simple way for teams to communicate with one another as without communication, there can’t be community! As such, at the very core of Teams are Rooms — persistent, Slack-like, chat rooms where students can communicate with one another. The first iteration of Teams and Rooms shipped in June 2015, a through the rest of this post, we’ll explore the engineering and product decisions we made in creating this product and how we’ve seen it improve the learning experience for students.

Teams — A Technical Perspective

One of the key elements of Teams is the web client where students log in to chat with one another. You can see what it looks like below.

In developing the web client for Team Rooms, we had a few specific engineering goals:

  1. The application had to be reactive. The view had to be able to update itself without a refresh in response to new events that occurred during the session (new messages, changes in status, new members joining etc).
  2. The application had to be simple to work on and extend as there would only be one to two engineers working on it (both frontend and backend).

Regarding our first goal, current practices in an MVC frontend (we use AngularJS), make creating a reactive application difficult. Let’s take a look at why this is the case. In a reactive application, controllers need to have a way of discovering when the model changes so that they can update the view. In the current paradigm, you have two main ways of achieving this:

1. Eventing + Callbacks

The first way you could approach this is by using a combination of eventing and callbacks to send out an event to controllers whenever you update the model and have the controllers run some callback in response. In practice, this is difficult to implement. A fine-grained eventing system requires a different event type for every property. Assuming you remember to emit those events on every change, the number of listeners also increases dramatically. In essence, the number of events and the number of listeners scales linearly with the number of model properties.

2. Watching Models

Alternatively, you could instead have your controllers check your model directly for changes by diffing the new from the old. But in practice, looking through a large nested object for changes is extremely slow and as more controllers attempt to do it, performance degrades significantly. If you have an object with let’s say a constant branching factor of m at each depth, and a depth of n, you will be watching m^n different properties which quickly becomes infeasible.

Our Solution: Store models in Immutable datastructures

Unsatisfied with the options we saw, we looked to solve this in a different way. Our goal was to create a system where controllers would just be able to say “I care about this part of the model only!”, and the model would update the controllers when it changed in a simple, performant manner.

Of course, checking if a standard Javascript object has changed is extremely expensive. But what if the object were immutable? Then, whenever the object changed, it would be trivial to know: you just see if oldObject === newObject and you’re done! In seeing this, we decided to store our state in an Immutable object, and we did so by using Facebook’s wonderful Immutable.js library. Now, we had a simple way of checking when the model changed — we just check the reference to the underlying immutable object! We then needed a way to communicate updates between the model and the controllers. Instead of using an eventing system, we chose to use Netflix’s ReactiveX library for javascript (RxJS). With Rxjs, we treat our model as a stream of states rather than a single, unchanging, state. Whenever we update the chat state model, we added a new value on a stream.
To understand this better, let’s walk through how a controller might subscribe to model changes. Let’s say we’re interested in showing a list of users in the chat room and want that list to update whenever the list of users changes. Let’s also say our users model has the following schema:

{'users':
    {
       userId: //user object,
       ...
    }
}

Here’s how the controller would do it:

  1. Get a stream of updates to the users model.
  2. // create a stream of user updates
    var userMaterializedView = chatState.createMaterializedView( // this is the main stream of updates the chat state chatState.chatStateUpdateObservable, // this is a function that returns the part of the chat state we care about function (newDB) { return chatState.getUsers(newDB); }
    );
  3. Subscribe to the stream and update the view whenever a new value comes on the stream.
  4. userMaterializedView.subscribe(function (users) { $scope.users = users;
    });

The diagram below visualizes what’s going on behind the scenes to help us create this stream.

The bottom stream of triangles, the ones that are all different from one another, is what the controller in this case gets. In this way, we created a simple API that allowed controllers to subscribe to updates on any transformation of the model they cared about.

The Backend: Powered By Layer

With a small team, we had the luxury to create this architecture on the frontend partly because we didn’t have to worry about the backend — Layer took care of most of that for us. As someone who’s configured and deployed a chat server from scratch before, using Ejabberd (an open source XMPP server), I can confidently say this saved us a lot of time. Managing an Ejabberd deployment on your own is challenging,and clustering the setup is quite difficult due to fairly opaque documentation that hasn’t been updated for a while. Additionally, Ejabberd isn’t particularly user friendly from a client perspective either. Being an XMPP server, it communicates over XML instead of JSON that your client can actually use. Moreover, it doesn’t support Websockets out of the box and you have to instead use the fairly ancient HTTP BOSH technology. As such, standard XMPP servers were not at all a great fit for our use case and came in the way of development.
On the other hand, Layer was a comparative joy. Layer gave us access to a simple Websocket API with extremely well documented json packets. This made communicating with the server very simple (see the documentation here). We didn’t have to worry about deployment, maintenance, or translating packets into something meaningful. What I personally loved most about Layer was that it was clearly designed to get development out of the way and allow you to focus on what’s most important — creating a wonderful experience for the user. My sentiments were shared by my colleagues working on the mobile clients — we were able to create a prototype android and iOS client in a matter of a weeks. I can’t think of another simple setup that would have provided us this kind of productivity. As a result, we were able to focus on our product and continuously iterate the user experience instead of debugging the server.

Improvements Since Launch

Since the basic product launched on July 6th, we’ve been able to improve it at a strong pace thanks to the productivity afforded us by Layer. Here are just a few of the enhancements we’ve made since launching less than two months ago.

Integration with Google Hangouts

While chat is good for communicating asynchronously, we found that nothing beats a live video chat for a realtime meeting. As such, we added in simple support for Google Hangouts which also gives our students the ability to screenshare with each other.

Udacity Guides

One of the most exciting things at Udacity is the growing community of intelligent, passionate alumni we have. We wanted to leverage this alumni network to help current students succeed even further. So we created the concept of guides — paid Udacity alumni who help teams learn, collaborate, and succeed.

We’ve been able to focus on improving the product in these ways largely thanks to the fact that we only had to worry about the client — Layer took care of the rest for us.

The Impact

As a result of this focus, we’ve seen metrics that lead us to believe that Teams are helping our students succeed in meaningful ways. For instance, the percent of students who submitted their first project, one of the key metrics of student engagement, increased by 25% — something we’ve never seen before. Not only this, we’ve seen groups connect, get to know one another, and create real bonds learning online. Layer’s tools and support have been an integral part of making this product happen and we are thrilled with our experience working with them. We are excited by the architecture we have put in place to create a truly reactive application for our students and know that the productivity afforded us by Layer was a key part in making this happen. We’ve still got a long way to go with Teams, but we’re confident that it can help us bring human connection to the forefront of online education.