Angular Server Side Rendering on AWS Lambda

This is an example of Angular Universal app running on AWS Lambda.
The finished sample code: jhmt/ng-ssr-on-aws-lambda

Prerequesites

Used frameworks/technologies

How does AWS Lambda render html?

Angular Universal works as a middleware for web server frameworks.
e.g. Express, Hapi and ASP.NET.
Meanwhile, an Express app can work on AWS Lambda with using “aws-serverless-express” node module provided by AWS. In this sample, AWS Lambda renders html with aws-serverless-express and Angular Express Engine.

Clone and run universal-starter

git clone https://github.com/angular/universal-starter.git

Prepare deploy

  1. Move “server.ts” and “webpack.server.config.js” in another folder (e.g. “/ssr”)
  2. Split the “listen” block into the new file to start in order to reuse the app on AWS Lambda.
  3. Modify “webpack:server” script in “package.json” to find the moved webpack file.
  4. Make sure “npm run build:ssr” works fine.

Initialize Serverless

  1. create “serverless.yml” file.
    serverless create --template aws-nodejs --name ng-ssr-aws-lambda
    

Create Lambda entry point

  1. create a new folder to store lambda function handler and webpack config (e.g. “lambda”)
  2. create a handler file (e.g. index.ts)
  3. create a webpack config file.

Modify serverless.yml file

  1. modify to route GET services to “lambda” folder
service: ng-ssr-aws-lambda
provider:
  name: aws
  runtime: nodejs6.10
  region: ap-southeast-2
  memorySize: 128
plugins:
- serverless-webpack
custom:
  webpack: lambda/webpack.config.js
functions:
  main:
    handler: lambda/index.handler
    events:
    - http:
        path: /
        method: get
    - http:
        path: "{proxy+}"
        method: get

Deploy

Make sure your AWS credentials have been set before deploy -
Serverless Framework - AWS Lambda Guide - Credentials

"npm run build:ssr && serverless deploy"
If you got the errors of "TS2304: Cannot find name 'AsyncIterator' or TS2304: Cannot find name 'AsyncIterable'", add the following in "tsconfig.json"
"lib": [
     "es2017",
     "dom",
     "esnext.asynciterable"
   ]

Create "Download" Link

“Download” link can be generated by HTML5 “a” tag with “download” attribute OR JavaScript code.

HTML5

<a href="./sample.txt" download="SAMPLE">Download</a>

This should work in most of modern browsers except IE. JavaScript code can replace this on these exceptions.

JavaScript

The file to download is saved in memory first with XMLHttpRequest and then saved as a file with URL.createObjectURL. (msSaveBlob in IE/Edge)

JavaScript Code

function downloadFile(url, filename) {

  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "blob";
  xhr.onload = function (oEvent) {
    var blob = xhr.response;
    if (window.navigator.msSaveBlob) {
      // IE/Edge
      window.navigator.msSaveBlob(blob, filename);
    }
    else {
      var objectURL = window.URL.createObjectURL(blob);
      var link = document.createElement("a");
      document.body.appendChild(link);
      link.href = objectURL;
      link.download = filename;
      link.click();
      document.body.removeChild(link);
    }
  };
  xhr.send();
}

HTML

<a href="./sample.txt" download="SAMPLE"
   onclick="javascript: downloadFile('./sample.txt', 'sample.txt'); return false;"
   >Download</a>

OOP Principles - SOLID

SOLID is an acronym of the following five basic principles of Object Oriented Programming and design.

1. Single Responsibility Principle


There should never be more than one reason for a class to change

What:

  • 2 reasons to change means 2 responsibilities.

Why:

  • A class with multiple responsibilities is likely unrobust.

Example:

  • Modem class



Responsibilities of Modem class can be separated into Managing connection(Connect/Disconnect) and Data communication(Send/Receive). If these responsibilities are modified separately, this class violates the SRP.

How:

  1. Design abiding by GRASP
  2. Work with design patterns: Proxy, Facade or Iterator
  3. TDD

2. Open-Closed Principle


Classes should be open for extension but closed for modification.

What:

  • “Open”
    • Extensible for behaviors
  • “Closed”
    • Changes to behaviors DO NOT affect to the source code or assemblies

Why:

  • For benefits of OOP: Flexibility, Reusability and Maintenancibility

Example:

  • Violated case



Client is using Server class. The Client class has to be changed to connect to another Server.

  • Open-Closed case:



No changes have to be made when the Server is modified.

How:

  1. Work with design patterns: Strategy or Template Method
  2. TDD

3. Liskov Substitution Principle


Subtypes must be substitutable for their base types.

What:

  • A method uses the base class can use any sub classes.
  • Designer/Programmer has to guarantee the behavior of sub class as same as the base class.

Why:

  • Violoation of LSP means violoation of OCP

Example:

  • Violated case



public void DrawShape(Shape shape)
{
    if (shape is Square)
    {
        Square sq = (Square)shape;
        sq.Draw();
    }
    else if (shape is Circle)
    {
        Circle c = (Circle)shape;
        c.Draw();
    }
}

Square and Circle classes cannot substitute Shape the base class.

How:

  1. Design by Contract

4. Interface Segregation Principle


Clients should not be forced to depend upon interfaces that they do not use.

What:

  • An interface should not have too many features. (That is called ‘fat’ interface.)

Why:

  • A client class has to implements all the ‘fat’ interface’s methods even if the class doesn’t need all of them. This could cause unnessesary modifications.

Example:

  • Violated case



IStorage interface has RollBack and Save methods. However, RollBack method is used only by DBStorage class.

How:

Split interfaces according to its feature.



5. Dependency Inversion Principle


High level modules should not depend upon low level modules. Both should depend upon abstractions. Secondly, abstractions should not depend upon details. Details should depend upon abstractions.

What:

  • A module should receive low level modules upon using them without having strong dependency.

Why:

  • A high level module depending on low level ones is affected by the low level modifications.
  • This should be inverted because high level should be superior.

Example:



High level modules don’t depend on low level modules.

How:

Design with DI(IoC) container.

Benefit of using IoC Container

Ioc Container provides some benefits when using Dependency Injection design pattern.

Let’s see sample code:

Interface and Class

public interface ISender
{
    void Send(string message);
}

public class EmailSender : ISender
{
    public void Send(string message)
    {
        Console.WriteLine("{0} has been sent via email.", message);
    }
}

public class CourierSender : ISender
{
    public void Send(string message)
    {
        Console.WriteLine("{0} has been sent via a courier.", message);
    }
}

Case 1: DI without Ioc Container

public class Messenger
{
    private ISender _sender;

    public Messenger(ISender sender)
    {
        _sender = sender;
    }

    public void Send(string message)
    {
        _sender.Send(message);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Send via email
        var messenger = new Messenger(new EmailSender());
        messenger.Send("Congrats!");
        // Send via courier
        messenger = new Messenger(new CourierSender());
        messenger.Send("Congrats!");
    }
}

This has the following problems:

  • The more Messenger class is used, the more new intantiation. This will cause less maintenancebility.
  • EmailSender and CourierSender depend on other classes. This makes instantiating them to each layer when they have hierarchy.
  • Less maintenancebility to changes to EmailSender and CourierSender structures.

IoC Container provides solutions for these problems.
Following is a sample usage of IoC container, Ninject. Ninject is avaliable on Nuget package manager.

Case 2: DI with Ioc Container

using Ninject;
class Program
{
    static void Main(string[] args)
    {
        // IoC Container
        var kernel = new StandardKernel();
        // Send via email
        kernel.Bind<ISender>().To<EmailSender>();
        kernel.Get<ISender>().Send("Congrats!");
        // Send via courier
        kernel.Rebind<ISender>().To<CourierSender>();
        kernel.Get<ISender>().Send("Congrats!");
    }
}

As shown in the sample code, IoC Conatiner provides some benefits:

  • Centralizing instantiation
  • Encapsulating class members
  • More testability with using mock code

These provide maintenancebility, flexibility and testability.

Performance Tuning with Window Function

Thinking of the best solution to get the product of each group which were registered earliest.

Product Table

| ProductId  | GroupId | RegisterDate | Price   |
|------------|---------|--------------|---------|
|      1     |   101   |  2016-01-01  | 250.00  |
|      2     |   101   |  2016-02-01  | 1200.00 |
|      3     |   101   |  2016-01-23  | 70.00   |
|      4     |   201   |  2016-01-14  | 800.00  |
|      5     |   201   |  2016-01-31  | 20.00   |
|      6     |   301   |  2016-10-01  | 25.00   |
|      7     |   301   |  2016-05-04  | 900.00  |
|      8     |   301   |  2016-12-01  | 750.00  |
|      9     |   301   |  2016-01-31  | 890.00  |
|     10     |   301   |  2016-11-11  | 750.00  |
|     11     |   401   |  2016-08-25  | 100.00  |

Expected Result

| GroupId | RegisterDate | Price  |
|---------|--------------|--------|
|   101   |  2016-01-01  | 250.00 |
|   201   |  2016-01-14  | 800.00 |
|   301   |  2016-01-31  | 890.00 |
|   401   |  2016-08-25  | 100.00 |

Solution 1

The easiest way to solve this is to use Join clause like this:


SELECT p1.GroupId, p1.RegisterDate, p1.Price
FROM Product AS p1
INNER JOIN (SELECT GroupId, MIN(RegisterDate) AS [MinDate] FROM Product GROUP BY GroupId) AS p2
ON p1.GroupId = p2.GroupId AND p1.RegisterDate = p2.MinDate;

Solution 1 has a performance problem because of following 4 reasons:

  • Sub queries tend to be stored in a temporary area (memory, disks, etc…). This will cause an overhead.
  • Sub queries don’t have indexes or constraint to optimize.
  • Join queries are more often than not expensive and have risks - Execute plan might be changed according to the scalability.
  • 2 scans to the table.

Avoid Join Query with Window Function

In this case, “ROW_NUMBER” window function enables us to avoid using join query.

Solution 2


SELECT p.GroupId, p.RegisterDate, p.Price
FROM 
(SELECT GroupId, RegisterDate, Price, 
 ROW_NUMBER() OVER (PARTITION BY GroupId ORDER BY RegisterDate) AS [rowNum]
FROM Product) AS p
WHERE p.rowNum = 1

Join Algorithm on SQL Server

Join Hints enforce query optimizer to use a specified join algorithm.

The characteristics of the algorithms are as follows:

Name Pros Cons
Nested Loops - Very fast on “Small driven table”
- Less consume of memory and disk (suitable for OLTP)
- Available for non-equijoins joins
- Unsuitable for large tables join
Hash - Suitable for large tables join - Expensive for memory (unsuitable for OLTP)
- Unavailable for non-equijoins joins
Sort Merge - Suitable for large tables join
- Available for non-equijoins joins
- Expensive for memory (unsuitable for OLTP)
- Unavailable for non-equijoins joins
- Inefficient fro unsorted tables.

Optimum algorithms

Based on table size, the proper algorithms for each join query are as follows:

  1. Small to Small
    “Nested Loops”

  2. Small to Large
    “Nested Loops” with using an index of the large table’s join key.
    In case of many rows, exchange the driven table from the small one to the large one. Or “Hash” algorithm might be better solution.

  3. Large to Large
    “Hash” should be the first try. If the join key has been sorted, then try “Sort Merge”

Database access from Razor view through IronPython

With IronPython, razor engine views can access to Database directly.

Razor View

@using IronPython.Hosting;
@using Microsoft.Scripting.Hosting;

@{
    ScriptEngine python = Python.CreateEngine();
    string path @"C:\scripts\hello.py";

    dynamic script = python.ExecuteFile(path);
    string msg = script.hello(1);
}

<h2>@msg</h2>

Python Script

import clr
clr.AddReference('System.Data')
from System.Data import *

def hello(id) :
    conn = SqlClient.SqlConnection("server=SERVER_ADDRESS;database=MyDB;uid=sa;password=PASSWORD")
    conn.Open()

    cmd = SqlClient.SqlCommand("SELECT Name FROM Member WHERE Id = @Id", conn)
    cmd.Parameters.Add("@Id", SqlDbType.Int).Value = id;
    reader = cmd.ExecuteReader()

    name = ''
    reader.Read()
    name = reader[0]

    reader.Close()
    conn.Close()

    return "Hello!" + name

Python Script in C#

Python Script can be called from C# with using IronPython.

Python Script

def hello(name) :
    return "Hello! " + name

C# code

using IronPython.Hosting;
using Microsoft.Scripting.Hosting;

void HelloPython
{
    ScriptEngine python = Python.CreateEngine();
    string path = @"C:\scripts\hello.py";

    dynamic script = python.ExecuteFile(path);
    Console.WriteLine(script.hello("John"));
}
> Hello! John

This can be used in cshtml view files like this:

@using IronPython.Hosting;
@using Microsoft.Scripting.Hosting;

@{
    ScriptEngine python = Python.CreateEngine();
    string path @"C:\scripts\hello.py";

    dynamic script = python.ExecuteFile(path);
    string msg = script.hello("John");
}

<h2>@msg</h2>