Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

AWS Adventures: Infrastructure as Code and Microservices (Part 2)

DZone's Guide to

AWS Adventures: Infrastructure as Code and Microservices (Part 2)

Building on the previous article, now it's time to put your credentials to use and start building your services, testing your code, and setting up your AWS roles.

· Cloud Zone
Free Resource

Download this eBook outlining the critical components of success for SaaS companies - and the new rules you need to play by.  Brought to you in partnership with NuoDB.

Welcome back to this massive AWS adventure! An AWS quest one might say. Now that you've got the basics set up, let's move onto the code for our project. We'll also be testing our work in just about every way possible, dipping into TDD as well. So, without further ado, let's dive back in.

Step 1: Code Setup

Make a folder, open your terminal, cd to the directory, run npm initand hit enter until it stops asking you questions, and finally run npm install aws-sdk --save. I’m using Node v7.3.0, but you can use whatever. Use nvm to switch to different versions at will.

Create a new JavaScript file called “index.js” and put in the following code. This is our basic lambda function that just returns a hello world message.

exports.handler = (event, context, callback) =>
{
    const response = {
        statusCode: '200',
        body: JSON.stringify({result: true, data: 'Hello from Lambda'}),
        headers: {
            'Content-Type': 'application/json',
        },
    }
    callback(null, response);
};


Step 2: Mad Creds

Create a new JavaScript file called “build.js”, and put in the following code.

const log    = console.log;
const AWS    = require('aws-sdk');
AWS.config.loadFromPath('./credentials.json');


That third statement is synchronous and must appear before you instantiate any other AWS objects/classes, else you’ll get weird authentication errors.

Step 3: TDD

Note the below are unit tests, not integration tests. Integration tests are tricky not to cause leaky state, so we’ll start with unit tests first. We’ll be using Mocha and Chai, but if you are a Tape or Jasmine fan, feel free to use those instead.

Run npm install mocha chai lodash --save. Open your package.json, and change the scripts section to look like this:

"scripts": {
    "test": "mocha build.test.js"
},


Create a new file called “build.test.js”, and put in the following code:

const mockLamba = {
    listFunctions: (params, cb) => cb(undefined, {"Functions": []})
};


Chai has various assertions, “should” reads better than “expect”, and we’ll be using Lodash for creating our own predicates. To avoid using Sinon, we’ll be making our functions as pure as possible. To do that, we’ll need a simple mock for the AWS Lambda client:

describe('#listFunctions', ()=>
{
    it('should give a list of functions', (done)=>
    {
        listFunctions(mockLamba, (err, data)=>
        {
            _.isArray(data.Functions).should.be.true;
            done();
        });
    });
});


Our first test is to read a list of functions:

describe('#listFunctions', ()=>
{
    it('should give a list of functions', (done)=>
    {
        listFunctions(mockLamba, (err, data)=>
        {
            _.isArray(data.Functions).should.be.true;
            done();
        });
    });
});


To run them, in your terminal type npm test and hit enter. You should get an error like “ReferenceError: listFunctions is not defined”.

Step 4: List Lambda Functions

Let’s define it:

const listFunctions = (lambda, callback)=>
{
    var params = {
        MaxItems: 50
    };
    lambda.listFunctions(params, (err, data)=>
    {
        if(err)
        {
            log("lambda::listFunctions error:", err);
            return callback(err);
        }
        callback(undefined, data);
    });
};


The listFunctions will list all the Lambda functions you have in your account. Here’s my personal play one:

Image title

Now re-run your test and she should be Corbin Dallas green.

Image title

Now let’s manually integration test to verify we can use the API in an authenticated way and our code works. Modify your listFunctionsso we can see the goodies that come back via a logging of the data:

lambda.listFunctions(params, (err, data)=>
{
    if(err)
    {
        log("lambda::listFunctions error:", err);
        return callback(err);
    }
    log("lambda::listFunctions, data:", data);
    callback(undefined, data);
});


Run node index.js and you should see some JSON in your Terminal.

Let’s add one more test for a negative scenario in case we don’t have permissions, bad credentials, etc.

const mockBadLambda = {
    listFunctions: (params, cb) => cb(new Error('boom'))
};
// ...
it('should blow up if something goes wrong', (done)=>
{
    listFunctions(mockBadLambda, (err, data)=>
    {
        err.should.exist;
        done();
    });
});


Re-run and you should have two passing tests.

Image title

Step 5: Create Lambda Function

Lambda functions require (Jesse Warden’s way) 4 things:

  1. A name prefix. You’ll often have at least 2 different functions to test development vs. production servers. So “myapp-dev” vs. “myapp-prod”. The “myapp” is your prefix.
  2. An alias. While AWS is implying they’d like you to use dev, qa, and prod, we’ll just use one called “defaultalias”. Sometimes this is referred to as a qualifier.
  3. A function name. It’s what you see in the left column of the aws console.
  4. The code, heh! I put mine in a zip file called “deploy.zip.” For now we’ll zip up our index.js file into a zip first. It’s easier to do this with shell, but THE HAMMER IS JS AND EVERYTHING IS A NAIL.
  5. A role.

Note On Roles (Feel Free to Skip)

My last article talked about creating a custom role, but we can use the default one Amazon provides called “lambda_basic_execution”. I’ll hardcode it for this tutorial. Note the number in it is your AWS account number. Given in a production environment this would something your team or a OPS/Cloud/Security team would create, we’ll create a variable for it vs. getting it dynamically. Roles are complicated and are the #1 thing to break your code next to security things (VPC, subnets, security groups).

Let’s now create a lambda function. In your build.test.js, first import it:

const {
    listFunctions,
    createFunction
} = require('./build');


Then add a new method to our Lambda mock:

createFunction: (params, cb) => cb(undefined, {"FunctionArn": 'default'})


When you create a new Lambda function, it’ll return a big ole JSON object mirroring mostly your parameters. The FunctionArn is the unique ID/URL of our function, so if we get this, we know she’s legit and it worked. We’ll add a new predicate to test for this String as well as a new test:

const legitString = (o) => _.isString(o) && o.length > 0;
const mockFS = {
    readFileSync: (filename)=> new Buffer('')
};
describe('#createFunction', ()=>
{
    it('should give us a positive response', (done)=>
    {
        createFunction(mockLamba, mockFS, (err, data)=>
        {
            legitString(data.FunctionArn).should.be.true;
            done();
        });
    });
});


Running it should fail:

Image title

Let’s make it pass. This’ll require a bit more variables setup first. Add your createFunction to the build.js:

const createFunction = (lambda, fs, callback)=>
{
    var params = {
        Code: {
            ZipFile: fs.readFileSync('deploy.zip')
        },
        FunctionName: FUNCTION_NAME,
        Handler: 'index.handler',
        Role: ROLE,
        Runtime: 'nodejs4.3'
    };
    lambda.createFunction(params, (err, data)=>
    {
        if(err)
        {
            // console.log("err:", err);
            return callback(err);
        }
        // log("data:", data);
        callback(undefined, data);
    });
};


And ensure you’ve exported the function below:

module.exports = {
    listFunctions,
    createFunction
};


Now re-run npm test and it should pass:

Image title

Let’s add a negative test scenario in case the createFunction fails. Add a createFunction to the mockBadLambda:

createFunction: (params, cb) => cb(new Error('boom'))


Then add the negative test for it:

it('should blow up if something goes wrong', (done)=>
{
    createFunction(mockBadLambda, mockFS, (err, data)=>
    {
        err.should.exist;
        done();
    });
});


Your tests run now should show:

Image title

Make and Destroy Deploy ZIP

Note Shell vs. JavaScript: While you can use a pure JavaScript solution using archiver (ensure you use forceZip64 in the constructor), now that PC’s have shell baked in, it’s easier to read, less code to write, and more predictable.

When you create a Lambda function, you need to upload code in the same call. Typically our code is more than just 1 index.js file, it’s many libraries in node_modules, configure files, etc. For file size reasons and ease of portability, we zip it all up into 1 smaller file. If you look at our createFunction you’ll see she reads it into a Node Buffer.

To create it and delete it ourself, we just need to add 2 new script commands to our package.json:

"makezip": "zip deploy.zip index.js",
"deletezip": "rm -f deploy.zip"


Test yourself by running npm run deletezip and npm run makezip.

Give createFunction a Spin

Let’s give her a go. Open up index.js, and at the very bottom, just go ahead and hardcode:

createFunction(lambda, fs);


You should now be able to log into the AWS Console and see your new Lambda function in the list:

Image title

Wunderbar! Now, delete your hardcoded createFunction from index.js.

That's All For Now

So, you've set up your functions (while keeping them as pure as possible), and you've been dutifully testing and fixing problems along the way. Next time, we're going to dive even more deeply into keeping everything tidy and testing your code as rigorously as possible. Stay tuned!

Learn how moving from a traditional, on-premises delivery model to a cloud-based, software-as-a-service (SaaS) strategy is a high-stakes, bet-the-company game for independent software vendors. Brought to you in partnership with NuoDB.

Topics:
aws ,cloud ,tutorial ,microservices ,infrastructure as code ,tdd ,functions ,aws lambda

Published at DZone with permission of Jesse Warden, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}