Building Scalable Web Apps with AWS Lambda and Home-Grown Serverless Web Framework
Feb 12, 2016 ~ 10 min read
There is a growing demand for enterprise software solutions that are easy to build and quick to deploy. Cloud Computing has emerged as one of the most viable routes for enterprise software delivery and, in our experience, Amazon Web Services is leading the charge.
There is a growing demand for enterprise software solutions that are easy to build and quick to deploy. Cloud Computing has emerged as one of the most viable routes for enterprise software delivery and, in our experience, Amazon Web Services is leading the charge. In this article, we will describe how we combined [1] Serverless Infrastructure from AWS with [2] Microservices Architecture to build and deploy a worry-free enterprise software marketplace we're calling the DEEP Marketplace.
Note: We will assume that you have general knowledge about below 2 concepts (or read suggested articles in parenthesis to learn more)
- Serverless Computing (learn more — Microservices without the Servers)
- Microservices Architecture (learn more — Microservices by Martin Fowler)
The Problem
Enterprise software is not attractive because traditionally it's slow, architecturally monolithic and cumbersome to operate. Enterprise IT organizations are commonly overwhelmed by the technology they maintain and there aren't enough hours in a day to address the backlog of work. That's why the process of buying typical enterprise solution that solves a given business problem must include an evaluation of how easily it can be customized for specific use case along with a thorough understanding of the ongoing costs of day-to-day operations. That's why savvy enterprise technology teams are constantly seeking software that empowers business users to be self-sufficient through self-service.
The Solution
At Mitoc Group, we believe that the most sustainable solution to enterprise software challenges is a marketplace of small pieces of functionality. Customers, especially non-technical users, can go to the marketplace, search for the functionality that they need, drag selected components into their shopping cart and push a button that will create a custom web application in their AWS account. This is similar to Lego Store, where customers buy lego pieces to build customized solutions at their will or pick from existing pre-packaged offers.
Building a Sample Application
In this post, we leverage open source software, while maintaining compliance with enterprise requirements. The sample application is a simple task management application, aka todo app, inspired by www.todomvc.com and accessible at todo.deep.mg. We will walk through the codebase and show how to build and deploy it as a scalable, yet serverless, custom web application.
Architecture
The architecture that we have been using for over six months now can be summarized as follows:
- Security: AWS IAM & Amazon Cognito
- Frontend: Amazon S3 & Amazon CloudFront
- Backend: AWS Lambda & Amazon API Gateway
- Database: Amazon DynamoDB, Amazon SQS & Amazon ElastiCache
The backbone of our approach is known as serverless microservices. We use AWS Lambda as our core computing service. Security by design is enforced by AWS IAM and we cache as much as possible, in every layer, to improve the performance and reduce the costs of running the web application.
Amazon S3 is only capable of serving static assets so we must rely on browser-based JavaScript to provide dynamic functionality. Our team selected AngularJS as frontend framework, but you can use any other JavaScript framework that you're most comfortable with.
We developed DEEP Framework — a home-grown and open-source serverless web framework — and supporting DEEP CLI — the development tool-chain — that has evolved through real-world use to increase our agility by:
- Streamlined local development
- Abstracted use of cloud services
- Automated allocation of infrastructure resources
- Zero devops and out-of-the-box scalability
So, let's get started…
You will need the following pre-requisites:
- AWS Account (learn how to Create an Amazon Web Services Account)
- AWS CLI (learn how to Configure AWS Command Line Interface)
- Node.js (learn how to Install Node.js)
- DEEP CLI (execute in command line: npm install deepify)
Next, deploy the todo app using the DEEP CLI:
- deepify install https://github.com/MitocGroup/deep-microservices-todo-app.git ~/deep-todo-app
- deepify server ~/deep-todo-app
- deepify deploy ~/deep-todo-app
Note: When step 2 (deepify server) is finished, you can open in your browser http://localhost:8000 and enjoy the todo app running locally.
We use a Services Oriented Architecture. Every frontend interaction is powered by a backend service that talks to a data tier. The web application is a collection of web components (we call them microapplications). Every web component consists of a frontend, backend, database, security, tests and docs. And every microapplication manages the entire technology stack, starting with infrastructure services, going up to platform services and concluding with application services, everything packaged as a well-structured codebase.
Note: Docs and tests are very important components of every microapplication, but we decided to leave them out of scope for this article.
Let's dive into the details of every tier next…
Building Frontend
The structure of our frontend is concentrated into: bootstrap.js, package.json and js/.
/* global System */
'use strict';
'format es6';
export default function todo() {
var deepAsset = DeepFramework.Kernel.container.get('asset');
return System.import(deepAsset.locate('@deep.todo:js/app/angular/index.js'));
}
{
"private": true,
"dependencies": {
"todomvc-app-css": "^2.0.0",
"todomvc-common": "^1.0.1",
"underscore": "^1.7.0"
}
}
js/
app/
angular/
controllers/
DeepTodoController.js
index.js
directives/
index.js
taskEscape.js
taskFocus.js
module/
index.js
ng-config.js
ng-module.js
ng-routes.js
services/
DeepTodoService.js
index.js
views/
layout.html
todo.html
index.js
name.js
As was mentioned earlier in this post, this todo app was inspired by www.todomvc.com. In practice, it means that we took the sample code from GitHub and ported using Hello World as reference. Reusing code allows us to be both consistent and fast.
Microservices architecture can be challenging sometimes. We found very useful the process of building a feature or a service first, and then break it down into microservices. Using this approach on todo app helped us take the code as it was, replace AngularJS and AngularUI Router modules with DEEP Microservices Root AngularJS, and transform the storage service using resource library from DEEP Framework.
Building Backend
The structure of our backend is reduced to: resources.json and src/.
{
"task": {
"create": {
"description": "Lambda that creates todo task",
"type": "lambda",
"methods": ["POST"],
"source": "src/Task/Create"
},
"retrieve": {
"description": "Lambda that retrieves todo task",
"type": "lambda",
"methods": ["GET"],
"source": "src/Task/Retrieve"
},
"delete": {
"description": "Lambda that deletes todo task",
"type": "lambda",
"methods": ["DELETE"],
"source": "src/Task/Delete"
},
"update": {
"description": "Lambda that updates todo task",
"type": "lambda",
"methods": ["PUT"],
"source": "src/Task/Update"
}
}
}
src/
Task/
Create/
Handler.es6
bootstrap.es6
package.json
Delete/
Handler.es6
bootstrap.es6
package.json
Retrieve/
Handler.es6
bootstrap.es6
package.json
Update/
Handler.es6
bootstrap.es6
package.json
When the UI and UX were defined, building the web services to support this frontend were intuitively easy. We are using RESTful APIs that provide CRUD (aka Create-Retrieve-Update-Delete) operations for each logically independent entity. In our case the entity is the todo task, and our todo app is supposed to offer functionality to create tasks, retrieve them, update or delete.
Each web service we use is very small, completely independent, highly decoupled, focused on context bounded one task, communicating through language-agnostic API. This is the very definition of a microservice, which we adopted by choice and by necessity. And this is where AWS Lambda excels!
Next, we will describe the structure of the "Create Todo" microservice that we deploy on AWS Lambda (the other microservices in this todo app are structured pretty much the same): bootstrap.es6, package.json and Handler.es6.
'use strict';
import DeepFramework from 'deep-framework';
import Handler from './Handler';
export default DeepFramework.LambdaHandler(Handler);
{
"name": "deep-todo-task-create",
"version": "0.0.1",
"description": "Create a new todo task",
"scripts": {
"postinstall": "npm run compile",
"compile": "deepify compile-es6 `pwd`"
},
"dependencies": {
"deep-framework": "^1.8.x"
},
"preferGlobal": false,
"private": true,
"analyze": true
}
'use strict';
import DeepFramework from 'deep-framework';
export default class extends DeepFramework.Core.AWS.Lambda.Runtime {
constructor(...args) {
super(...args);
}
handle(request) {
let TodoModel = this.kernel.get('db').get('Todo');
TodoModel.createItem(request.data, (err, todo) => {
if (err) {
throw new DeepFramework.Core.Exception.DatabaseOperationException(err);
}
return this.createResponse(todo.get()).send();
});
}
}
Building Database
The structure of our database model is trivial, just one file: Todo.json.
{
"Title": "string",
"Completed": "boolean"
}
Since we're using a NoSQL database like Amazon DynamoDB, the only piece of information that the database library from DEEP Framework cares about is the table name and the primary key. Everything else is handled by the library itself, as long as it's in json format.
Enforcing Security
The beauty of cloud-native approach is that security comes by design. When we deploy todo app, deepify automatically identifies the web services involved in the process and creates corresponding security roles. On backend and database tiers these roles are transposed into AWS IAM roles, while on frontend tier we automatically compile them and expose in the UI as Access Control Lists (also known as ACLs). Let's see some examples:
{
"Version": "2015-10-07",
"Statement": [
{
"Effect": "Allow",
"Action": ["deep.todo:task:create"],
"Resource": ["deep.todo:task"]
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["lambda:InvokeFunction"],
"Resource": ["arn:aws:lambda:us-east-1:123456789000:function:DeepProdTodoCreate1234abcd*"]
}
]
}
Putting Everything Together
Now that we're done with frontend, backend, database and security, it's time to glue everything together and put all web components into action:
DeepTodo/
Backend/
Data/
Docs/
Frontend/
Tests/
deepkg.json
hook.init.js
parameters.json
There are two more components in the root path of the microapplication that are critical: deepkg.json and parameters.json. Similar to Node Package Manager (also know as npm), our command line tool DEEP CLI (also known as deepify) is looking for metadata (in this case — deepkg.json) and configuration (in this case — parameters.json) files. Additionally, deepify can be enriched with hooks (in this case — hook.init.js) that allow flexibility and customization of the deployment process:
{
"identifier": "deep.todo",
"name": "DeepTodo",
"description": "DeepTodo Microapplication",
"version": "0.0.1",
"propertyRoot": false,
"author": {
"name": "Mitoc Group",
"email": "hello@mitocgroup.com",
"website": "http://www.mitocgroup.com"
},
"contributors": [
{
"name": "DEEP Team",
"email": "hello@deep.mg",
"website": "https://www.deep.mg"
}
],
"frontendEngine": ["angular"],
"dependencies": {
}
}
{
"frontend": {},
"backend": {}
}
'use strict';
var exports = module.exports = function(callback) {
var exec = require("child_process").exec;
var path = require('path');
var source = path.join(__dirname, 'Frontend/learn.json');
var dist = path.join(__dirname, '../DeepNgRoot/Frontend');
exec('cp ' + source + ' ' + dist, function(error, stdout, stderr) {
if (error) {
console.error('Error while copying learn.json', error);
callback();
return;
}
console.log('learn.json was successfully copied into DeepNgRoot');
callback();
})
};
Hopefully the deployment execution that was described in Getting Started section is done by now. If everything went well, you will see 2 URLs in the last rows of the terminal's output window: S3 Website Endpoint and CloudFront Distribution. Open in the browser any of the URLs and you'll see the todo app running in your own AWS account.
Note: The distribution takes up to 20 minutes to provision, therefore don't worry if it returns an HTTP error in the first couple of minutes.
Below video playlist summarizes the steps that we've walked you through:
Scalability Benchmarking
Even though SPAs (aka Single-Page Applications) are challenging to monitor and benchmark, we have load tested the todo app in order to associate some numbers to how big it can scale using out-of-the-box configurations. We found that standard setup gives us acceptable response time (within 2 seconds) for up to 10,000 concurrent users per second, at "my Starbucks coffee costs more" price tag:
Concurrency (in users/sec) | Capacity Peak (in reads/sec) | Average Response (in ms) | Total Requests (in 1min) | Average Cost (in $/min) |
---|---|---|---|---|
100 | 14 | 529 | 5,984 | $0.000013 |
1,000 | 18 | 1,003 | 59,446 | $0.000141 |
5,000 | 12 | 1,724 | 219,027 | $0.000825 |
10,000 | 17 | 2,152 | 274,518 | $0.001898 |
Note: These numbers can be improved with additional complexity and features that are beyond the scope of this article
Key Takeaways
- The sample web application described in this article is a simple example of how we are building and deploying enterprise software using serverless microservices on AWS
- DEEP Todo App is powered by DEEP Framework. It consists of 1 microapplication that interacts with 6 microservices and is resolving another microapplication as dependency
- DEEP Marketplace is powered by DEEP Framework. It consists of 12 microapplications that interacts with 64 microservices and is resolving several layers of dependencies across multiple microapplications
- DEEP Framework is a serverless web framework that abstracts web apps and web services from specific cloud services, enabling developers build cloud-native applications or platforms using microservices architecture
- Backend's resources are implemented in Node.js. AWS Lambda supports also Java and Python, but our process is not able yet to use them streamlined because deepify uses only npm, and doesn't support mvn or pip yet
- Frontend's structure is designed to allow developers write code using any JavaScript framework they are comfortable with. More than that, it is possible to have multiple implementations of the same UI and UX using different Javascript frameworks and storing in the same codebase. For example:
- js/app/angular — implementation of todo app using AngularJS
- js/app/react — implementation of todo app using React
- js/app/backbone — implementation of todo app using Backbone.js
Closing Thoughts
In this post, we've shown you how to build and deploy scalable web apps worry-free. We walked through the process of building and deploying frontend, backend, database and security of scalable web applications. You can fork the sample code repository as a starting point for your own custom web application powered by serverless microservices.
And if you have questions or suggestions, please don't hesitate to contact us.
Recommended Posts
See All PostsBuilding Enterprise Level Web Applications on AWS Lambda with the DEEP Framework
Since the beginning, Mitoc Group has been building web applications for enterprise customers. We are a small group of developers who are helping customers with their entire web development process, from conception through execution and down to maintenance.
Apr 14, 2016
Learn moreMitoc Group featured as Frameworks Partner by AWS Lambda team
We are very humble and honored to be featured by AWS Lambda as one of their Framework Partners. On behalf of our customers, we'd like to take this opportunity to thank AWS Lambda team for their continuous trust and support.
Feb 26, 2016
Learn moreInterview: Creators of The DEEP Framework
Today, we'll hear from the creators of the DEEP Framework about its origins, what it's great at, and how to start using it. The big driver for the Mitoc Group behind going serverless was to reduce ongoing maintenance. What better way to maintain fewer servers by not having them?
Dec 16, 2015
Learn more