Arrow Functions

In JavaScript, functions are normally declared with the function keyword, like so:

function helloWorld() { }

var helloWorld = function() { };

However, you may have run into the following scenario before: you have an object with a function that calls something asynchronously– such as an AJAX request– that must call *another* function on that object once the asynchronous call completes. This is where we run into an issue with JavaScript’s scope behavior.

Dynamic Scope in JavaScript

There are a couple of ways to do this. First is to cache a reference to the this variable before the asynchronous call:

class Dog {
    bark() {
        var self = this;

        request
          .get('/bark')
          .success(function(result) {
              self.finishBark();
          });
    }

    finishBark() {

    }
}

The issue is, inside of the success callback, the variable this actually is not an instance of the Dog class. In JavaScript the this variable’s scope is dynamic. That is, what this is actually depends on who is the caller of the function rather than where the function is actually defined in the code.

Another way to fix this is to use the bind function:

class Dog {
    bark() {
        var self = this;

        request
          .get('/bark')
          .success(this.finishBark.bind(this));
    }

    finishBark() {

    }
}

Instead of defining a separate function for the success callback, we are calling bind on the finishBark function. When you call bind on a function, a new function is returned with the scope bound to the parameter passed into the bind function call. In this case, we are passing in the instance of the Dog class into the bind call so that the code behaves similarly to our first example.

Arrow Functions

ES6 provides a convenient new way to define functions while simultaneously fixing the dynamic scope issue. The arrow function, also called a “fat arrow” function, can be defined without the function keyword. Rather, it uses a set of parenthesis and the “fat arrow” to define the function:

function add(a, b) {
    return a + b;
}

// ... is the same as ...

var add = (a, b) => {
    return a + b;
}

// ... which is also the same as ...

var add = (a, b) => a + b

This may seem confusing at first, but it’s quite simple– we define the method signature with a simple set of parenthesis and the parameters enclosed within. In the above examples, the parameters for the function are “a” and “b”. Second, the function body is declared inside a set of curly braces.

Notice in the first example that we include both curly braces and the return keyword. However, both of these are omitted in the second example. This is because arrow functions are designed to be more compact, and therefore have a couple of optional features.

If the function you declare is a single statement, you can omit both the curly braces and the return keyword. Instead, you can place the statement directly after the fat arrow like so:

var add = (a, b) => a + b;
var subtract = (a, b) => a - b;
var multiply = (a, b) => a * b;
var divide = (a, b) => a / b;

This is very convenient when you use the fat arrow functions as map, filter, or reduce functions. Compare the following implementations, for example:

[1, 2, 3, 4, 5]
    .map(function(number) {
        return number * 3;
    })
    .filter(function(number) {
        return number % 2 == 1
    })
    .reduce(function(carry, number) {
        return carry + number;
    }, 0);

[1, 2, 3, 4, 5]
    .map((number) => number * 3)
    .filter((number) => number % 2 == 1)
    .reduce((carry, number) => carry + number, 0);

As you can see, the arrow function notation is significantly easier to read and much more succinct.

Lexical Scope

We previously reviewed the concept of “dynamic scope” in JavaScript– the scope, or this object, depends on what calls the function, not where the function is defined. Normally we do not care about this behavior, or even use it to our advantage, but there’s many places where you just want the scope of an inner function to not change.

The arrow functions will automatically bind their scope to that where the function is defined. In a way, this is very similar to making a call to bind.

class Dog {
    bark() {
        request
          .get('/bark')
          .success(function(res) {
              this.finishBark(res);
          }.bind(this));

        // ... is essentially equivalent to this ...
        request
          .get('/bark')
          .success((res) => {
              this.finishBark(res);
          });
    }

    finishBark(result) {

    }
}

Because the scope is bound automatically with the arrow function, we can get rid of the ugly call to bind, which makes the code more concise and readable.

Preface

ES6 is not an entirely new language– rather, it’s a logical extension of the JavaScript we all know. Because of this, before proceeding with the course, it is strongly recommended to have a moderate to advanced understanding of the existing syntax and behavior of JavaScript, including topics such as the prototype-based nature of the language.

Additionally, you should have a strong understanding of command line tools in general and at least a basic familiarity with JavaScript task runners such as Grunt or Gulp.

Static and Instance Variables in Classes

Though the ES6 does not support static and instance variables, there is a proposal for ES7 that would enable this functionality.

The ES7 property proposal would allow for both static and instance variables to be declared on a class:

class Animal {
    /**
     * Declare "legs" as a static property of animal with a default value of 4
     */
    static legs = 4;

    /**
     * Declare the "name" property of animal without a value
     */
    name;
}

All static and instance variables must be declared directly inside of the class. Static variables are simply declared by prefixing the variable name with static. If you wish to include an initial value, you may specify the value by assigning it as you would a normal variable. If you do not want to include an initial value, you may just specify the variable name followed by a semicolon to end the line.

Introduction to Classes

ES6 introduces the concept of classes. Though it has been possible to imitate this behavior for some time with prototypes, ES6 simply wraps the prototype model using a clearer syntax.

Previously, you could use JavaScript’s prototype model to create a class-like object using the Function type.

function Animal {
    // Constructor
    this.name = "Generic Animal";
};

Animal.prototype.move = function() {
    // Move the animal
};

var genericAnimal = new Animal();

Despite this approach being perfectly acceptable, the complexity quickly increases if you attempt to perform some sort of class extension.

function Dog {
    Animal.call(this);

    this.name = "Dog";
};

Dog.prototype = Object.create(Animal.prototype);

Dog.prototype.wagTail = function() {
    // Wag tail
};

Dog.prototype.move = function() {
    Animal.prototype.move.call(this);
    
    // Dogs cannot move without wagging their tails
    this.wagTail();
};

var dog = new Dog();

alert(dog.name); // Alert "Dog"
dog.move(); // Moves the dog and wags its tail

This can easily be made more concise with ES6 classes.

class Animal {
    constructor() {
        this.name = "Generic Animal";
    }

    // ... other methods are implemented
}

class Dog extends Animal {
    constructor() {
        super();

        this.name = "Dog";
    }

    wagTail() { // Wag tail }

    move() {
        super.move();

        this.wagTail();
    }
}

Static Methods

You can also add static methods to classes with the static keyword.

class LanguageUtilities {
    static translate(str) {
        // Perform the translation
    }
}

LanguageUtilities.translate("Hello, world!");

Static and Instance Variables

The ES6 specification does not support declaring static and instance variables directly. However, you can set initial values for instance variables inside of the constructor, like so:

class Animal {
    constructor() {
        this.legs = 4;

        this.laysEggs = false;
    }
}

However, there is a stage 1 proposal for ES7 that enables declaration of static and instance variables. More information on this can be found later in the course.

Introduction to Javascript and CSS Optimization

Though images often are the largest assets on a page, Javascript and CSS assets are often the culprits of a slow loading web page. There are several different reasons why this could be, including inefficient code, having a large number of assets on a page, and little or no caching.

We’ll primarily focus on the latter two cases– code optimization is an extremely complex topic and is largely dependent on the website or application in question, though we will address concepts such as asynchronous asset loading.

Resource Bundling

Javascript and CSS files traditionally contain small to medium-sized blocks of code for one particular task. For example, a website traditionally might include a Javascript library or two, the website’s Javascript, and one or more third-party scripts. However, a large number of these scripts can be condensed into a single bundle which reduces the number of network requests required to load the page.

Resource Caching

Another source of latency in a website is often low (or zero) cache time for static assets. With improper cache settings, a web browser will make unnecessary requests to fetch content it should already have in its cache.

Code Loading

There are many ways to load Javascript or CSS into a web page: from simple HTML tags, to dynamic script inject, to asynchronous loading. How you should load your assets largely depends on what the purpose of the asset is, as well as where and when you wish to have the asset loaded.

Sprite Sheets

Sprite sheets can also be a good way to reduce the number of network requests that are made when someone visits your website. In essence, a sprite sheet is a single image file that contains many or all of the icons, buttons, or other graphics your website might need. For example, an E-Commerce site may have a sprite sheet containing a shopping cart icon, a credit card icon, and other iconography that’s used throughout the site– all in a single image, side by side.

If all of these separate graphics are in a single image file, then how could a web designer possibly use them in different locations on the website? The answer lies within a feature of CSS: the background-position CSS property.

In your CSS files, you might already have images set as the background of an element. A button, for example, might have a background image used to style it visually. The background position property allows for a web designer to move the actual image around– by default, the value is set to 0px 0px: no offset. However, what if we move the background image around?

Remember that E-Commerce site I mentioned earlier? A sprite sheet the site might use could look something like this:

Example Icon Sprite Sheet

As you can see, there’s nothing really special about the sprite sheet in this case. All the sprite sheet contains is a grid full of icons.

Now, if we want to display the shopping cart icon we can use the background position property of CSS. We know that each icon is in a grid of 16×16 pixels, meaning that the first icon’s top left hand corner starts at 0px 0px and ends in the bottom right at 16px 16px. The second icon, however, begins with its top left hand corner at 16px 0px, while it ends with its bottom right hand corner at 32px 16px. Notice that the CSS background position is structured as X position, Y position. There are several other presets available, including textual positions such as top left or center bottom, and percentages are also supported.

Given the above information, the E-Commerce website sprite sheet would be paired with the following CSS:

.icon {
  background-image: url(img/icons.png);
  background-repeat: no-repeat;

  width: 16px;
  height: 16px;
}

.icon.cart {
  background-position: 0px 0px;
}

.icon.user {
  background-position: 16px 0px;
}

You can see the top left hand position of the icon is set as the background position in the CSS, while the width and height are also set for every .icon.

Generating Sprite Sheets

Of course, these sprite sheets could be generated by hand. In fact, this is sometimes the easiest way to use sprite sheets for simple grids of icons or small sprite sheets. But, sometimes sprite sheets can become large and complicated– there might be icons that aren’t exactly in a grid, or that are different sizes. In these cases, you have a couple different options:

Usually these tools allow you to upload an image and simply draw boxes around your sprites, generating the required CSS for you. Apps such as SpriteRight also have several other features, including CSS importing, and automatic packing.

Adding Your First Rancher Worker Host

The Rancher platform is split into two parts– the management server, and worker hosts. The management server hosts the UI for starting, stopping, and editing services, where the worker hosts actually run the Docker containers that compose the services.

For a production setup using cloud providers such as Digital Ocean or Amazon Web Services EC2, it’s easy to just use the UI to add hosts. You can do this through the administration panel by clicking “Infrastructure” in the menu bar at the top of the page, and then clicking “Hosts” in the submenu. From here, you can add a new host with the “Add Host” button.

Digital Ocean

Configuring a new host on Digital Ocean is quite easy. Before you can actually add the host, you should retrieve an API key from your API settings in Digital Ocean. Ensure you grant both read and write access to this new key.

Don’t have a Digital Ocean account? Get started with $10 in free credit— enough to run two 512 MB servers for a month!

Once you have this API key, you can enter it in the form and fill out the remaining information.

Add a Digital Ocean Host to Rancher

Also, make sure you enter a name for your server or Rancher will assign a name for you (the names Rancher assigns to servers do not look very pretty and are hard to distinguish from one another). I also recommend choosing an instance size of 1 GB or greater, along with enabling backups if applicable.

You may also want to consider distributing your hosts across different data center. Because Rancher’s inbuilt networking works across hosts and data centers, you can have one machine in NY 1, one in NY 2, and one in NY 3 to enable high availability for your infrastructure. That way, if a data center (or two, depending on how many redundant services you run) go down for whatever reason, your service will still be available.

You can also set labels for your host, which allow you to schedule different services to these hosts. For example, you may want to label one of your machines as “lb = true”. This would then allow you to launch a load balancer service on hosts labeled as “lb = true”. Or, you could consider labeling your machines with their drive type (e.g. “storage = ssd”) or, with physical servers, their rack name/number. All of these properties are optionally taken into account when scheduling your services to place them on the physical hosts desired.

Once you click “Create”, Rancher launches and adds the node to your cluster automatically. Just give it a few minutes to show up under the “Hosts” page, and then you can start some services.

Amazon Web Services EC2

From the Rancher UI, you can also add new hosts on Amazon EC2. To do so, you will need an IAM key pair that allows Rancher full access to EC2. You can generate this key pair from the IAM console on AWS. From the IAM console, navigate to the “Users” page and click the blue “Add New Users” button.

AWS IAM - Add New Users

Enter a username (or user names, if you wish to create more than one user), and enable the “Generate an access key for each user” check box. This will give you an AWS access key and AWS access secret that you will pass to Rancher.

An AWS access key pair is a set of tokens that act like a secondary username and password. These keys only provide the ability to perform certain actions that you define, so if they happened to be compromised the damage would be more limited than if an intruder leaks your username and password. You can create, revoke, and delete these key pairs at any time through the IAM console

After you’ve copied the access key and secret, you will need to apply a policy to the user. An IAM policy will allow for the access key pair to perform the actions defined in the policy. To do so, find the user you just created and click on it. Once you’re on the user’s information page, you can click “Attach Policy” under “Managed Policies”.

AWS IAM Attach Policy to User

Simply find the policy titled “AmazonEC2FullAccess”, check the box, and apply the policy. Now, you can go back to Rancher and enter your access key and secret you saved earlier into the Rancher UI.

Add an EC2 Host to Rancher

From this point, you simply need to fill out the required information– such as the name of the host and the instance size– to create the host. Any AWS instance size should work for Rancher, since the minimum amount of RAM included in a current generation AWS server is 1 GB.

Using Your Management Server as a Worker

So far, you have at least two servers running– one management server, which holds the UI for Rancher, and one or more worker hosts. However, for small clusters, the idle server manager host can be put to use as a worker host as well.

To do so, simply add a new host from the management panel as previously described, except choose “Custom” as the provider.

You’ll be provided with a set of instructions to add a Docker host to your Rancher cluster. These directions can also be used to add bare metal servers, non-cloud virtual servers, or cloud servers on providers not supported by the Rancher UI.

However, adding the management server as a Rancher worker is a special case and the instructions need to be modified– the command needs to have an environmental variable set to set the CATTLE_AGENT_IP to the current management server IP.

For example, consider if Rancher provided you with the following command:

sudo docker run -d --privileged -v /var/run/docker.sock:/var/run/docker.sock rancher/agent:v0.7.10 http://123.123.5.5:8080/v1/scripts/HEXCHARACTERS:NUMBERS:RANDOMSTRING

You would need to modify this command to look like the following:

sudo docker run -d --privileged -e CATTLE_AGENT_IP=123.123.5.5 -v /var/run/docker.sock:/var/run/docker.sock rancher/agent:v0.7.10 http://123.123.5.5:8080/v1/scripts/HEXCHARACTERS:NUMBERS:RANDOMSTRING

Notice the addition of the flag -e CATTLE_AGENT_IP=123.123.5.5. If you’re not familiar with Docker commands, this simply passes an environmental variable to the Docker container. Ensure you change the IP address in this environmental variable to the IP address of your management server.

Once you SSH into your management server and run the command, give Rancher a few minutes to discover the new agent. You should now see the Rancher management server in the dashboard as a host available to run Docker services.

Configuring Rancher

Once you have created a server with Rancher on it, you’ll have to wait a minute or two before the software completes initializing. During this time, it’s a good idea to configure DNS so that you can access your Rancher server from a friendly DNS name.

You’ll know Rancher has finished initialization once you can access the endpoint [your server IP/hostname]:8080.

By default, Rancher does not have an authentication mechanism enabled. However, it’s quite easy to enable GitHub authentication so that only you or your team can access Rancher. To do so, click the red banner at the top of your Rancher dashboard and follow the provided instructions to create a GitHub application identifier and secret.

Rancher Initial Setup

Even though you, and everyone who uses your Rancher installation, currently need a GitHub account to authenticate, you do not need to actually pay for GitHub private repositories or use it for source code hosting.

Installing Rancher

Before you install Rancher, you’ll need a server to install it on. You can install Rancher on a bare-metal server, a VPS, or cloud server– the only requirement is that the machine can run Docker.

Digital Ocean

You can easily get started on cloud providers such as Digital Ocean, that offer a pre-Dockerized Ubuntu system with a single click. If you don’t have an account with Digital Ocean, you can get started for free with a $10 credit. That’s enough to run two 512 MB servers for a month.

Once you’ve created an account, simply click the green “Create Droplet” button in the top right hand corner of the dashboard– a “Droplet” is Digital Ocean’s terminology for a virtual server.

Go ahead and enter a host name, choose a Droplet size (1 GB is the recommended minimum, especially if you’re going to use this node to both run the management console and your own services), and region. For the image, you can select “Docker 1.x.x on Ubuntu 14.04”. As of publication, the current version of Docker is “1.7.1”.

Docker 1.7.1 Image on Digital Ocean
Selecting the Docker 1.7.1 Image on Digital Ocean

Under the “Available Settings” section there are a couple checkboxes. You can choose whether to enable backups (which is advised if you plan to use this Rancher installation for an actual production service) or enable private networking. Ensure you select the “User Data” box, and enter the following script:

#!/bin/bash

docker run -d --restart=always -p 8080:8080 rancher/server

The User Data script allows for you to configure your instance at first boot. There are a couple different types of scripts you can run, including Bash scripts as indicated by the #!/bin/bash. All we need is for the Rancher server container to be launched, binding to port 8080 so that it is accessible to the outside web.

Amazon Web Services

You can also install Rancher on AWS EC2 relatively easily. You can simply create a new instance with the Ubuntu 14.04 AMI.

Ubuntu 14.04 AMI on AWS EC2

Because the Ubuntu AMI doesn’t include Docker by default, we have to install it with a User Data script. You can find the User Data input inside of the “Advanced Details” section of the “Configure Instance” step.

#!/bin/bash

wget -qO- https://get.docker.com/ | sh
docker run -d --restart=always -p 8080:8080 rancher/server

As you can see, the User Details script is somewhat similar to the one that was required for Digital Ocean. The only difference here is that we install Docker before we install Rancher.

Security Group

Because AWS EC2 is protected by a user-definable firewall, called Security Groups, you must configure it to allow traffic to specific ports. Ideally, the following Security Group inbound settings should be used:

  • TCP Port 22 (allow all)
  • TCP Port 80 (allow all)
  • TCP Port 443 (allow all)
  • TCP Port 8080 (allow all, or only allow your business’ or home IP address– this is the management console)
  • UDP Port 500 (this security group only)
  • UDP Port 4500 (this security group only)

Ensure you also select a key pair you have access to, since you will need to SSH into the machine.

Introduction to Rancher

Docker is fantastic for building a scalable infrastructure. Not only does it force you to isolate your application into reasonable chunks, but it also encourages you to build these pieces as stateless services. This is fantastic for high availability and scalability, but actually scaling out a pure Docker-based infrastructure can be difficult if done manually.

Docker Swarm and Compose are the “official” solutions to this problem– they allow for you to build a giant, elastic Docker cluster that appears as a single machine to your client. Additionally, Compose allows you to scale your application easily to multiple instances.

Despite this, these two components are lacking a couple critical features– cross-machine service discovery, as well as a built in load balancer that distributes traffic to your scaled Docker infrastructure.

Tutum is a service that adds these remaining components, and to great success. Though you can use your own nodes with Tutum, sometimes it’s desirable to use your own, self-hosted service.

Rancher is an open source Docker PaaS that includes features like service discovery and DNS, load balancing, multi-node support, cross-host networking, health checks, multi-tenancy, and more. Essentially, Rancher takes all the features of Tutum and packs it into a single Docker container that can be hosted on your own nodes so that you have complete control.

Even better, Rancher is extremely easy to install and often can be done in a matter of minutes.