<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Willis Tech]]></title><description><![CDATA[Full Stack & Cloud]]></description><link>https://www.willischou.com/</link><image><url>https://www.willischou.com/favicon.png</url><title>Willis Tech</title><link>https://www.willischou.com/</link></image><generator>Ghost 2.9</generator><lastBuildDate>Sun, 31 Mar 2024 10:18:40 GMT</lastBuildDate><atom:link href="https://www.willischou.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Serverless Framework with custom authoriser locally (episode 2)]]></title><description><![CDATA[Develop API Gateway with Custom Authoriser locally - Serverless Framework]]></description><link>https://www.willischou.com/aws-serverless-using-serverless-framework-with-custom-authoriser-locally/</link><guid isPermaLink="false">Ghost__Post__65c7c3c4f3784850027031d7</guid><category><![CDATA[Serverless]]></category><category><![CDATA[Serverless Framework]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[Lambda]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 11 Feb 2024 00:55:19 GMT</pubDate><media:content url="https://www.willischou.com/images/2024/02/serverless-framework-cover.png" medium="image"/><content:encoded><![CDATA[<h1 id="background">Background</h1><img src="https://www.willischou.com/images/2024/02/serverless-framework-cover.png" alt="Serverless Framework with custom authoriser locally (episode 2)"/><p>Booyakasha! &#x1F91C;</p><p>This is the second episode of the AWS Serverless series. In the <a href="https://www.willischou.com/aws-serverless-using-sam-with-custom-authoriser-locally/?ref=willis-tech">first episode</a>, we walked through an example of AWS SAM and a custom authorizer.</p><p>Today, we are going to explore another approach - Serverless Framework!<br>Serverless Framework has <a href="https://www.serverless.com/plugins/serverless-offline?ref=willis-tech">serverless-offline</a> and <a href="https://www.serverless.com/plugins/serverless-esbuild?ref=willis-tech">serverless-esbuild</a> plugins, which can help you write Lambda in TypeScript and run it with custom authoriser locally.</br></p><h1 id="tldr">TL;DR</h1><ul><li>Using the Serverless Framework with <a href="https://www.serverless.com/plugins/serverless-offline?ref=willis-tech">serverless-offline</a> plugin can simulate the Lambda and API Gateway locally. </li><li>Using Serverless (version 1.72.0 or later), you can write Infrastructure as Code in TypeScript.</li><li>Grab the working example here<br><a href="https://github.com/Willis0826/serverless-local-authorizer-example?ref=willis-tech">https://github.com/Willis0826/serverless-local-authorizer-example</a> (Using TypeScript)</br></li></ul><h1 id="lets-walk-through-it">Let&apos;s walk through it</h1><p>We are going to implement a <code>/ping</code> API behind an API Gateway and Custom Authoriser using TypeScript.</p><p>The architecture diagram as following:</p><!--kg-card-begin: markdown--><p><img src="https://www.willischou.com/images/2023/08/sam-api-gateway-custom-authoriser-example.drawio.png" alt="Serverless Framework with custom authoriser locally (episode 2)" loading="lazy"/></p>
<!--kg-card-end: markdown--><p>If you have read the <a href="https://www.willischou.com/aws-serverless-using-sam-with-custom-authoriser-locally/?ref=willis-tech">first episode</a>, you will notice that we are doing the same thing here, but with the Serverless Framework.</p><p>We have five steps to implement for this:</p><ol><li>Setting Up Serverless Plugins</li><li>Creating a Basic API Gateway</li><li>Creating a Ping Lambda</li><li>Creating an Authoriser Lambda</li><li>Verifying the Result</li></ol><h2 id="setting-up-serverless-plugins">Setting Up Serverless Plugins</h2><p>We are going to use the following two plugins to create a seamless development experience:</p><ul><li><a href="https://www.serverless.com/plugins/serverless-offline?ref=willis-tech">serverless-offline</a> - Simulate API Gateway and Lambdas locally</li><li><a href="https://www.serverless.com/plugins/serverless-esbuild?ref=willis-tech">serverless-esbuild</a> - Compile TypeScript to JavaScript</li></ul><p>You can use the serverless CLI to generate a new project. However, I recommend cloning the repository <a href="https://github.com/Willis0826/serverless-local-authorizer-example?ref=willis-tech">serverless-local-authorizer-example</a> to get a workable example.</p><p>Once you have a Serverless project, you can install these packages.</p><pre><code class="language-Shell">$ npm install --save-dev @serverless/typescript 
$ npm install --save-dev serverless-esbuild
$ npm install --save-dev serverless-offline
$ npm install --save-dev ts-node tsconfig-paths typescript</code></pre><p>All set, you can now create a <code>{project_root_path}/serverless.ts</code> file with the following content.</p><pre><code class="language-TypeScript">import type { AWS } from &apos;@serverless/typescript&apos;;

const serverlessConfiguration: AWS = {
  service: &apos;api&apos;,
  frameworkVersion: &apos;3&apos;,
  plugins: [&apos;serverless-esbuild&apos;, &apos;serverless-offline&apos;],
  provider: {},
  functions: {},
  package: { individually: true },
  custom: {
    esbuild: {
      bundle: true,
      minify: true,
      sourcemap: true,
      exclude: [&apos;aws-sdk&apos;],
      target: &apos;node20&apos;,
      define: { &apos;require.resolve&apos;: undefined },
      platform: &apos;node&apos;,
      concurrency: 10,
    },
  },
};

module.exports = serverlessConfiguration;
</code></pre><h2 id="creating-a-basic-api-gateway">Creating a Basic API Gateway</h2><p>Let&apos;s define a very simple API Gateway and some general settings in the <code>provider</code> field of &#xA0;<code>{project_root_path}/serverless.ts</code>.</p><pre><code>...

const serverlessConfiguration: AWS = {
  service: &apos;api&apos;,
  frameworkVersion: &apos;3&apos;,
  plugins: [ ... ],
  provider: {
    name: &apos;aws&apos;,
    region: &quot;ap-southeast-1&quot;,
    runtime: &apos;nodejs20.x&apos;,
    apiGateway: {
      shouldStartNameWithService: true,
    },
    environment: {},
  },
  functions: {},
  package: { ... },
  custom: {
    ...
  },
};

...</code></pre><h2 id="creating-a-ping-lambda">Creating a Ping Lambda</h2><p>Now, we are going to add the first Ping Lambda to this API Gateway.</p><p>We need the following three files for the Ping Lambda.</p><ul><li><code>src/functions/ping/handler.ts</code> a handler returns 200 and a <code>pong</code> message.</li></ul><pre><code class="language-TypeScript">import { APIGatewayProxyHandler } from &apos;aws-lambda&apos;;

export const lambdaHandler: APIGatewayProxyHandler = async (_event, _context) =&gt; {
  return {
    statusCode: 200,
    body: &quot;pong&quot;,
  }
};</code></pre><ul><li><code>src/functions/ping/index.ts</code> a serverless definition for the Lambda.</li></ul><pre><code class="language-TypeScript">import { handlerPath } from &apos;@libs/handler-resolver&apos;;

export default {
  handler: `${handlerPath(__dirname)}/handler.lambdaHandler`,
  events: [
    {
      http: {
        method: &apos;get&apos;,
        path: &apos;ping&apos;,
        authorizer: &apos;authorizer&apos;,
      },
    },
  ],
};</code></pre><ul><li><code>src/libs/handler-resolver.ts</code> a helper function to get the current path.</li></ul><pre><code class="language-TypeScript">export const handlerPath = (context: string) =&gt; {
  return `${context.split(process.cwd())[1].substring(1).replace(/\\/g, &apos;/&apos;)}`
};</code></pre><p>Now, we&apos;ve created the Ping Lambda and set the authorizer to a function named <code>authorizer</code>.</p><p>Next step, we need to reference this Lambda in the <code>serverless.ts</code></p><pre><code class="language-TypeScript">import type { AWS } from &apos;@serverless/typescript&apos;;

import ping from &apos;@functions/ping&apos;;

...
const serverlessConfiguration: AWS = {
  ...
  functions: {
    ping,
  },
  ...
}
...</code></pre><p>You may have noticed that we use <code>@libs</code> and <code>@serverless</code> while importing stuff. Please follow the <a href="https://github.com/Willis0826/serverless-local-authorizer-example/blob/main/tsconfig.json?ref=willis-tech">tsconfig.json</a> configuration if you fancy this setup.</p><h2 id="creating-an-authoriser-lambda">Creating an Authoriser Lambda</h2><p>Alright, we are almost there!</p><p>We can create an authoriser that does nothing but returning a policy to allow <code>GET/ping</code> request.</p><ul><li><code>src/functions/authorizer/handler.ts</code> a handler acts as a Custom Authoriser.</li></ul><pre><code class="language-TypeScript">import { APIGatewayTokenAuthorizerHandler, APIGatewayAuthorizerResult } from &apos;aws-lambda&apos;;

export const lambdaHandler: APIGatewayTokenAuthorizerHandler = async (_event, _context) =&gt; {
  return generateAdminPolicy();
};

const generateAdminPolicy = () =&gt; {
  const authResponse: APIGatewayAuthorizerResult = {
    principalId: `systemadmin`, // you can assign principalId if you want
    policyDocument: {
      Version: &apos;2012-10-17&apos;,
      Statement: [
        {
          Action: &apos;execute-api:Invoke&apos;,
          Effect: &apos;Allow&apos;,
          Resource: &apos;arn:aws:execute-api:*:*:*/*/GET/ping&apos;, // allow access GET/ping
        },
      ],
    },
  };
  return authResponse;
}
</code></pre><ul><li><code>src/functions/authorizer/index.ts</code> a serverless definition for the Lambda.</li></ul><pre><code class="language-TypeScript">import { handlerPath } from &apos;@libs/handler-resolver&apos;;

export default {
  handler: `${handlerPath(__dirname)}/handler.lambdaHandler`,
};
</code></pre><p>Don&apos;t forget to reference this Authorizer Lambda in the <code>serverless.ts</code>.</p><pre><code class="language-TypeScript">import type { AWS } from &apos;@serverless/typescript&apos;;

import authorizer from &apos;@functions/authorizer&apos;;
import ping from &apos;@functions/ping&apos;;

...
const serverlessConfiguration: AWS = {
  ...
  functions: {
    authorizer,
    ping,
  },
  ...
}
...</code></pre><p>Wicked! You have set up everything. &#x270C;&#xFE0F;</p><h2 id="verifying-the-result">Verifying the Result</h2><p>Let&apos;s run the following command to spin up the API Gateway and Lambda locally:</p><pre><code class="language-Shell"># If you haven&apos;t got the Serverless CLI installed
npm install -g serverless

serverless offline start</code></pre><!--kg-card-begin: markdown--><p><img src="https://www.willischou.com/images/2024/02/serverless-offline.png" alt="Serverless Framework with custom authoriser locally (episode 2)" loading="lazy"/></p>
<!--kg-card-end: markdown--><p>Your API Gateway is listening on port 3000 with the path prefix <code>/dev</code>. Use the following command to send a request and see it in action.</p><pre><code class="language-Shell">curl -H &quot;Authorization: abc&quot; http://127.0.0.1:3000/dev/ping</code></pre><!--kg-card-begin: markdown--><p><img src="https://www.willischou.com/images/2024/02/serverless-offline-response.png" alt="Serverless Framework with custom authoriser locally (episode 2)" loading="lazy"/></p>
<!--kg-card-end: markdown--><p>Cheers! &#x1F37A; You can develop AWS Lambda locally now.</p><h1 id="conclusion">Conclusion</h1><p>We have now covered AWS SAM and Serverless Framework. Both tools support local development and deployment. The remaining question is, how do you choose between these two options?</p><p>Personally, I select the options based on these conditions.</p><p>&#x1F31F; Serverless Framework</p><ul><li>Fast local development experience.</li><li>Ability to write infrastructure as code in TypeScript.</li><li>Support for other cloud providers.</li></ul><p>&#x1F31F; AWS SAM</p><ul><li>Comfort with CloudFormation.</li><li>Focus on AWS and the ability to configure detailed settings.</li></ul>]]></content:encoded></item><item><title><![CDATA[SAM with custom authoriser locally (episode 1)]]></title><description><![CDATA[Develop API Gateway with Custom Authoriser locally - AWS SAM]]></description><link>https://www.willischou.com/aws-serverless-using-sam-with-custom-authoriser-locally/</link><guid isPermaLink="false">Ghost__Post__64d58b76f74a6d7912440a06</guid><category><![CDATA[Serverless]]></category><category><![CDATA[AWS SAM]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[Lambda]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Fri, 11 Aug 2023 02:04:22 GMT</pubDate><media:content url="https://www.willischou.com/images/2024/02/aws-sam-cover.png" medium="image"/><content:encoded><![CDATA[<h1 id="background">Background</h1><img src="https://www.willischou.com/images/2024/02/aws-sam-cover.png" alt="SAM with custom authoriser locally (episode 1)"/><p>When you want to develop an AWS Serverless application, you may have heard of this powerful tool - <a href="https://github.com/aws/aws-sam-cli?ref=willis-tech">SAM CLI</a> (Serverless Application Model). You can use SAM to create a classic serverless application (including API Gateway and Lambda) on AWS. Also, it can simulate the AWS environment on your local environment.</p><p>Recently, SAM CLI <a href="https://github.com/aws/aws-sam-cli/issues/137?ref=willis-tech">added a useful functionality to support simulating API Gateway Custom Authoriser locally</a>. Before this feature was implemented, it can be painful to develop an API Gateway with Custom Authoriser. This post will walk you through an example, and help you use this new feature with ease!</p><h1 id="tldr">TL;DR</h1><ul><li>Grab the working example and play around here <a href="https://github.com/Willis0826/sam-local-authorizer-example?ref=willis-tech">https://github.com/Willis0826/sam-local-authorizer-example</a> (Using TypeScript)</li></ul><h1 id="lets-walk-through-it">Let&apos;s walk through it</h1><p>We are going to create an API called <code>Ping</code> with TypeScript. When you invoke this API with path <code>/ping</code>, your request goes through a custom authoriser called <code>Authorizer</code>. If the custom authoriser returns a valid response, your request can reach the Lambda of <code>Ping</code> API.</p><p>The architecture diagram as following:</p><!--kg-card-begin: markdown--><p><img src="https://www.willischou.com/images/2023/08/sam-api-gateway-custom-authoriser-example.drawio.png" alt="SAM with custom authoriser locally (episode 1)" loading="lazy"/></p>
<!--kg-card-end: markdown--><p>We have four steps to implement this example:</p><ol><li>Creating a Basic API Gateway</li><li>Creating a Ping Lambda</li><li>Creating an Authoriser Lambda</li><li>Verifying the Result</li></ol><h2 id="creating-a-basic-api-gateway">Creating a Basic API Gateway</h2><p>You can create a brand new project with <code>sam init</code> and remove the stuff you don&apos;t need. Or, if you want a minimal working project with TypeScript, you can clone <a href="https://github.com/Willis0826/sam-local-authorizer-example?ref=willis-tech">sam-local-authorizer-example</a>.</p><p>After you got a SAM project, the first thing you need to do is defining an API Gateway in <code>{project_root_path}/template.yaml</code>.</p><pre><code class="language-YAML">AWSTemplateFormatVersion: 2010-09-09
Description: &gt;-
  sam-local-authorizer-example
Transform:
- AWS::Serverless-2016-10-31

Resources:
  # API Gateway
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: dev
      Auth:
        # CORS setting
        AddDefaultAuthorizerToCorsPreflight: false
        ResourcePolicy:
          CustomStatements: [
              {
                &quot;Effect&quot;: &quot;Allow&quot;,
                &quot;Principal&quot;: &quot;*&quot;,
                &quot;Action&quot;: &quot;execute-api:Invoke&quot;,
                &quot;Resource&quot;: &quot;execute-api:/*/OPTIONS/*&quot;,
              },
            ]</code></pre><h2 id="creating-a-ping-lambda">Creating a Ping Lambda</h2><p>We are going to create a Ping Lambda which response <code>pong</code> when it&apos;s invoked. Also, we are going to explore how to develop Lambda with TypeScript.</p><p>Let&apos;s add the Ping Lambda first!</p><p>Create a new file <code>{project_root_path}/src/handlers/ping.ts</code> with the following codes.</p><pre><code class="language-TypeScript">import { APIGatewayProxyHandler } from &apos;aws-lambda&apos;;

export const lambdaHandler: APIGatewayProxyHandler = async (event, context) =&gt; {
    return {
        statusCode: 200,
        body: &quot;pong&quot;,
    }
};</code></pre><p>In order to expose the Ping Lambda we created, we need to create another file <code>{project_root_path}/src/app.ts</code> with the following codes.</p><pre><code class="language-TypeScript">import { lambdaHandler as PingHandler } from &apos;./handlers/ping&apos;;


export {
    PingHandler,
}</code></pre><p>Now, we got a function ready to handle the Lambda invocation event. But, this is a TypeScript Lambda, we need a file <code>{project_root_path}/package.json</code> as well. The following code is an example of <code>package.json</code></p><pre><code class="language-JSON">{
  &quot;name&quot;: &quot;sam_local_authorizer_example&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;lambda&quot;,
  &quot;main&quot;: &quot;app.js&quot;,
  &quot;repository&quot;: &quot;&quot;,
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;dependencies&quot;: {},
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;sam build&quot;,
    &quot;deploy&quot;: &quot;sam deploy&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@tsconfig/node18&quot;: &quot;^1.0.0&quot;,
    &quot;@types/aws-lambda&quot;: &quot;^8.10.73&quot;,
    &quot;@types/node&quot;: &quot;^18.0.0&quot;,
    &quot;typescript&quot;: &quot;^4.2.3&quot;
  }
}</code></pre><p>Finally, let&apos;s define this Ping Lambda by adding a new <code>AWS::Serverless::Function</code> resource under <code>Resources</code> section in <code>{project_root_path}/template.yaml</code>.</p><pre><code class="language-YAML">... Others
Resources:
  ... API Gateway
  # Protected API Lambda
  Ping:
    Type: AWS::Serverless::Function
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2020
        SourceMap: false
        External:
          - node_modules
        EntryPoints:
          - src/app.ts
    Properties:
      Runtime: nodejs18.x
      CodeUri: ./
      Handler: app.PingHandler
      Events:
        Api:
          Type: Api
          Properties:
            Auth:
              ApiKeyRequired: true
            RestApiId:
              Ref: ApiGateway
            Path: /ping
            Method: get</code></pre><p>As you can see, the <code>Ping</code> resource has a section <code>Metadata</code> that tells SAM to use <code>esbuild</code> for your Lambda. Before you run <code>sam build</code>, you may need to make sure you install the <code>esbuild</code> beforehand.</p><p>You can use the following command to install <code>esbuild</code> and try to build the Ping Lambda.</p><pre><code class="language-Shell">npm install -g esbuild
sam build</code></pre><p>Now, you should be able to see a new folder <code>{project_root_path}/.aws-sam/build/Ping/app.js</code> contains the transpiled JavaScript.</p><h2 id="creating-an-authoriser-lambda">Creating an Authoriser Lambda</h2><p>We are going to create a simple Authoriser Lambda in &#xA0;<code>{project_root_path}/src/handlers/authorizer.ts</code>. This Lambda allows all requests to access <code>GET/ping</code> API.</p><pre><code class="language-TypeScript">import { APIGatewayTokenAuthorizerHandler, APIGatewayAuthorizerResult } from &apos;aws-lambda&apos;;

export const lambdaHandler: APIGatewayTokenAuthorizerHandler = async (event, context) =&gt; {
  return generateAdminPolicy();
};

const generateAdminPolicy = () =&gt; {
  const authResponse: APIGatewayAuthorizerResult = {
    principalId: `systemadmin`,
    policyDocument: {
      Version: &apos;2012-10-17&apos;,
      Statement: [
        {
          Action: &apos;execute-api:Invoke&apos;,
          Effect: &apos;Allow&apos;,
          Resource: &apos;arn:aws:execute-api:*:*:*/*/GET/ping&apos;,
        },
      ],
    },
  };
  return authResponse;
}
</code></pre><p>After we created the Authoriser Lambda, we need to expose this Lambda in <code>{project_root_path}/src/app.ts</code> as well.</p><pre><code class="language-TypeScript">import { lambdaHandler as AuthorizerHandler } from &apos;./handlers/authorizer&apos;;
import { lambdaHandler as PingHandler } from &apos;./handlers/ping&apos;;


export {
    AuthorizerHandler,
    PingHandler,
}</code></pre><p>We are almost there! &#x1F6A9;</p><p>Let&apos;s define the Authoriser Lambda in <code>{project_root_path}/template.yaml</code>. In order to make the SAM work with Authoriser Lambda locally, the definition of <code>template.yaml</code> is crucial.</p><p>We need to add two attributes <code>DefaultAuthorizer</code> and <code>Authorizers</code> to <code>AWS::Serverless::Api</code> resource. </p><p>Then, we need to define <code>Authorizer</code> Lambda under <code>Resources</code> section.</p><pre><code class="language-YAML">... Others
Resources:
  # API Gateway
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: dev
      Auth:
        ... Others
        DefaultAuthorizer: LambdaTokenAuthorizer
        Authorizers:
          LambdaTokenAuthorizer:
            FunctionPayloadType: TOKEN
            FunctionArn: !GetAtt Authorizer.Arn
            Identity:
              Header: Authorization
              ReauthorizeEvery: 300
  # Auth Lambda
  Authorizer:
    Type: AWS::Serverless::Function
    Metadata:
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: es2020
        SourceMap: false
        External:
          - node_modules
        EntryPoints:
          - src/app.ts
    Properties:
      Runtime: nodejs18.x
      CodeUri: ./
      Handler: app.AuthorizerHandler
      Timeout: 5
   ... Ping Lambda</code></pre><p>Done!&#x1F389; You are all set. Let&apos;s jump to the next step to verify the result.</p><h2 id="verifying-the-result">Verifying the Result</h2><p>Now, you can run the following command to simulate API Gateway and Lambda Authoriser locally.</p><pre><code class="language-Shell">sam build
sam local start-api
</code></pre><!--kg-card-begin: markdown--><p><img src="https://www.willischou.com/images/2023/08/Screenshot-2023-08-11-220411.png" alt="SAM with custom authoriser locally (episode 1)" loading="lazy"/></p>
<!--kg-card-end: markdown--><p>Your API Gateway is listening on port 3000. Using the following command to send a request and see it in action.</p><pre><code class="language-Shell">curl -H &quot;Authorization: abc&quot; http://127.0.0.1:3000/ping</code></pre><!--kg-card-begin: markdown--><p><img src="https://www.willischou.com/images/2023/08/Screenshot-2023-08-11-220803.png" alt="SAM with custom authoriser locally (episode 1)" loading="lazy"/></p>
<!--kg-card-end: markdown--><p>Cheers! &#x1F37A; That&apos;s all. Enjoy the API Gateway and Custom Authoriser locally.</p><h1 id="what-is-next">What Is Next?</h1><p>Check the <a href="https://www.willischou.com/aws-serverless-using-serverless-framework-with-custom-authoriser-locally/?ref=willis-tech">AWS Serverless - Using Serverless Framework with custom authoriser locally (episode 2)</a> to find out another amazing tool.</p>]]></content:encoded></item><item><title><![CDATA[AWS Serverless - Feature Flags]]></title><description><![CDATA[Implement Feature flags with AWS serverless architecture]]></description><link>https://www.willischou.com/feature-flags/</link><guid isPermaLink="false">Ghost__Post__64c634d349177b6ceb270808</guid><category><![CDATA[AWS]]></category><category><![CDATA[DynamoDB]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[Serverless]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 30 Jul 2023 21:43:38 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/ff-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="background-of-this-project">Background of this project</h2>
<img src="https://www.willischou.com/images/2023/08/ff-cover.jpg" alt="AWS Serverless - Feature Flags"/><p>Lately, I was implementing a feature flags functionality to enable us selectively deliver new features to users. Also, the whole system uses AWS serverless architecture, including API Gateway, Lambda, and DynamoDB.</p>
<p>I assume you are familiar with <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html?ref=willis-tech">API Gateway custom authroiser</a> and <a href="https://aws.amazon.com/pm/dynamodb/?trk=8576349e-b904-471e-ad2e-136548085999&amp;sc_channel=ps&amp;ef_id=CjwKCAjwlJimBhAsEiwA1hrp5mm5UeDBuTrB5Q0isTo_6QTxcW0yiXNUcMuO2RNVBJR6ZTZM3AKN9xoCAQ0QAvD_BwE%3AG%3As&amp;s_kwcid=AL%214422%213%21639556471883%21e%21%21g%21%21aws+dynamodb%2119153973926%21149787895568&amp;ref=willis-tech">DynamoDB</a>.</p>
<p>There are many options you can implement feature flags, we explore two approaches in this article and compare them.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="solutions">Solutions</h2>
<p>We have two approaches to implementing feature flags. Let&apos;s start with the easiest one.</p>
<h3 id="1-retrieve-and-verify-in-custom-authroiser">1. Retrieve and verify in custom authroiser</h3>
<p>The simplest solution is, we retrieve and verify the user&apos;s feature flags in the custom authroiser. With this approach, each time a user access an API, we validate the request with the latest feature flags. That means when we turn on a feature flag for a user, that user can access the new feature instantly.</p>
<p>The architecture as follows:<br>
<img src="https://www.willischou.com/images/2023/07/ff-general.png" alt="AWS Serverless - Feature Flags" loading="lazy"/></br></p>
<p>Pros:</p>
<ul>
<li>Turning off/on feature flags always reflects instantly.</li>
<li>The implementation is straight-forward.</li>
</ul>
<p>Cons:</p>
<ul>
<li>Adding an extra latency in the custom authroiser to retrieve feature flags.</li>
<li>The latency in custom authroiser impacts all APIs.</li>
</ul>
<h3 id="2-retrieve-from-jwt-and-verify-in-customer-authroiser">2. Retrieve from JWT and verify in customer authroiser</h3>
<p>Wicked! This is the most interesting part. In order to overcome the extra latency in the customer authoriser, we embed the feature flags in JWT which users got when they logged in. When a user access an API, we validate the feature flags in JWT. By doing so, we only need to retrieve the feature flags from the database when a user login. That means we remove the extra latency in the custom authoriser. But, the disadvantage is if we turn on/off a feature flag and want it to take effect immediately, we need to invalidate the JWT of the user.</p>
<p>The architecture as follows:<br>
<img src="https://www.willischou.com/images/2023/07/ff-with-jwt.png" alt="AWS Serverless - Feature Flags" loading="lazy"><br>
Pros:</br></img></br></p>
<ul>
<li>No extra latency in the custom authroiser.</li>
<li>Less performance overhead.</li>
</ul>
<p>Cons:</p>
<ul>
<li>The implementation is slightly complicated since you need to invalidate the JWT of a user when you want to refresh the feature flags.</li>
<li>Turning off/on feature flags doesn&apos;t reflect directly. However, the user must log in again and get the new JWT.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="database-design">Database Design</h2>
<p>We use DynamoDB as our database to store feature flags. Below is the schema of the <code>FeatureFlags</code> DynamoDB table.</p>
<table>
<thead>
<tr>
<th>user_id (PK)</th>
<th>feature_flag(SK)</th>
<th>is_enabled</th>
<th>updated_at</th>
<th>created_at</th>
</tr>
</thead>
<tbody>
<tr>
<td>system_global_admin</td>
<td>new_feature_A</td>
<td>false</td>
<td>1687882000</td>
<td>1687882000</td>
</tr>
<tr>
<td>user_A</td>
<td>new_feature_A</td>
<td>true</td>
<td>1687882010</td>
<td>1687882010</td>
</tr>
</tbody>
</table>
<br>
<p>The above table contains two feature flags. The one with <code>system_global_admin</code> is a global feature flag, this feature flag is applied to all the users by default. The one with <code>user_A</code> is a user-specific feature flag. The a user-specific feature flag has higher priority.</p>
<p>In this article, we use a dedicated table for feature flags. However, it is a good practice to store your feature flags with other data you need. You can read through <a href="https://www.alexdebrie.com/posts/dynamodb-single-table/?ref=willis-tech#what-is-single-table-design">The What, Why, and When of Single-Table Design with DynamoDB</a> to find out more detail.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="api-design">API Design</h2>
<p>You probably want to create an API for the frontend to retrieve feature flags. This API should combine the global feature flags and user-specific feature flags in one response.</p>
<p>In the following example, if a user <code>user_A</code> has a feature flag:</p>
<pre><code>{
  &quot;new_feature_A&quot;: true
}
</code></pre>
<p>and we have two global feature flags:</p>
<pre><code>{
  &quot;new_feature_A&quot;: false,
  &quot;new_feature_B&quot;: false
}
</code></pre>
<p>the API of retrieving <code>user_A</code> feature flags should return the following response:</p>
<pre><code>{
  &quot;new_feature_A&quot;: true,
  &quot;new_feature_B&quot;: false
}
</code></pre>
<p>That means we can toggle feature flags with the global scope or per-user scope!</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="conclusion">Conclusion</h2>
<p>In the early stage, it should be fine to go with the first solution. You implement the straightforward solution and get the benefit of feature flags as soon as possible. However, if you want to reduce the latency, it&apos;s worth looking into the second solution.</p>
<p>Embedding feature flags in JWT is an inspiring idea for me. We use the nature of JWT to carry the information we want. Hope this inspires you as well.</p>
<p>The feature flags can include two scopes: global and user-specific. This can give you more control over the whole system.</p>
<!--kg-card-end: markdown--></br>]]></content:encoded></item><item><title><![CDATA[FinTech - Global latency sensitive service on EKS]]></title><description><![CDATA[Architecture for a global latency sensitive service.]]></description><link>https://www.willischou.com/fintech-global-latency-sensitive-service-on-eks/</link><guid isPermaLink="false">Ghost__Post__63fb86df83025200f28d53e4</guid><category><![CDATA[EKS]]></category><category><![CDATA[EC2]]></category><category><![CDATA[Blockchain]]></category><category><![CDATA[FluxCD]]></category><category><![CDATA[HelmRelease]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 16:28:20 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/global-latency-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="background-of-the-service">Background of the service</h2>
<ul>
<li>A global latency sensitive service is running on AWS across multiple regions.</li>
<li>Some components are stateful. We make sure the stateful components have a backup node which can take over the workload when there is a failure .</li>
<li>Some components have long-lived TCP connection. We need to gracefully close the connection before we do release.</li>
<li>To reduce the latency as much as possible, we deploy our services in six regions.</li>
</ul>
<img src="https://www.willischou.com/images/2023/08/global-latency-cover.jpg" alt="FinTech - Global latency sensitive service on EKS"/><p><img src="https://www.willischou.com/images/2023/03/global-arch.png" alt="FinTech - Global latency sensitive service on EKS" loading="lazy"><br>
Architecture for a latency sensitive service.</br></img></p>
<h3 id="route-53-traffic-policy">Route 53 Traffic Policy</h3>
<p><a href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/traffic-flow.html?ref=willis-tech">Traffic policy</a> provides geo-based DNS resolution. This can be useful to route traffic between different cloud providers.</p>
<h3 id="global-accelerator">Global Accelerator</h3>
<p>[Endpoint group] provides a entrypoint for your service in specific AWS region. You can route traffic between different AWS regions.</p>
<h3 id="data-layer">Data Layer</h3>
<p>In general use case, we can put the database in the same region with the service to have the lowest latency. But, this results you need to have a application to aggregate the data from many databases in order to present the aggregated data for users.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="how-do-we-deploy-services-in-eks">How do we deploy services in EKS?</h2>
<ul>
<li>Used StatefulSet for stateful components. Also, some components have a MySQL sidecar container, it needs <code>volumeClaimTemplates</code> to help it persist the data.</li>
<li>Used EKS to serve components with large disk volume. We need to use <strong>AWS Backup</strong> service to <strong>make sure the data in the EBS volume has a daily snapshot.</strong></li>
<li>Used EKS with EFS to share disk between specific services. For example, a service A will parse the logs generated by service B. In this case, we use EFS to share logs file between service A and service B.</li>
</ul>
<pre><code class="language-yaml">apiVersion: helm.fluxcd.io/v1
kind: HelmRelease
metadata:
  name: example-app
  namespace: app
  annotations:
    fluxcd.io/automated: &quot;true&quot;
spec:
  releaseName: example-app
  chart:
    git: ssh://git@github.com/my-org/helm-charts
    ref: app_1.0.0
    path: charts/app
  values:
    image:
      repository: xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/app
      tag: &quot;xxxxxxxxxxxxxxxxxxxxxx&quot;
    targetgroupbinding:
      ... truncated ...
    serviceAccount:
      enabled: true
      irsaRoleArn: &quot;arn:aws:iam::xxxxxxxxxxxxx:role/app_irsa_role&quot;
    configuration:
      environment: &quot;prod&quot; &lt;- use variables to retrieve specific configs
      region: &quot;frankfurt&quot;
</code></pre>
<p>This is an example for deploying an service in EKS across many regions.<br>
The helm chart covers all the details and let you easily scale your service.</br></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[FinTech - How to monitor API error rate?]]></title><description><![CDATA[Build a API path-level monitoring solution with exporter and prometheus.]]></description><link>https://www.willischou.com/fintech-how-our-api-error-rate-monitoring-was-implemented/</link><guid isPermaLink="false">Ghost__Post__63fb825983025200f28d53bd</guid><category><![CDATA[prometheus-nginxlog-exporter]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[Prometheus]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 16:04:41 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/monitor-api-error-rate-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h3 id="high-level-of-the-api-traffic">High level of the API traffic</h3>
<img src="https://www.willischou.com/images/2023/08/monitor-api-error-rate-cover.jpg" alt="FinTech - How to monitor API error rate?"/><p><img src="https://www.willischou.com/images/2023/03/api-traffic.png" alt="FinTech - How to monitor API error rate?" loading="lazy"><br>
This diagram shows the route of one API request. We can collect request information at the &#x201C;Nginx&#x201D; stage, and the information has response time and status code.</br></img></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="what-happen-inside-the-nginx">What happen inside the Nginx?</h3>
<p><img src="https://www.willischou.com/images/2023/03/nginxlog-exporter.png" alt="FinTech - How to monitor API error rate?" loading="lazy"/></p>
<p>The Nginx instance has two process : Nginx and nginxlog-exporter.</p>
<p>Nginx will write the request information to log file, and then nginxlog-exporter watch that log file to aggregate the information based on URI.</p>
<p>Here is an example of <code>exporter.access.log.string</code> log file</p>
<pre><code class="language-jsx">app.com /api/blog/1 GET 200 0.002
app.com /api/blog/2 GET 200 0.008
app.com /api/blog/3 GET 200 0.027
app.com /api/user/1 GET 200 0.003
app.com /api/user/2 GET 404 0.003
</code></pre>
<p>These log can tell us some informations:</p>
<ul>
<li>How long the request takes?</li>
<li>Is this request succeed with status code 200?</li>
</ul>
<p>Logs format:</p>
<pre><code class="language-bash">${domain} ${uri} ${http_method} ${http_status_code} ${request_time}
</code></pre>
<p>Finally, we need to transform the logs to metrics in order to monitor it.</p>
<p>nginxlog-exporter helps us to archive this! We just give it a config file like below.</p>
<pre><code class="language-yaml">namespaces:
  - name: dispatcher_nginx
    format: &quot;$domain $uri $http_method $http_status_code $request_time&quot;
    source_files:
      - /var/log/nginx/exporter.access.log.string
    ...
    aggregrateuri:
      # blog related api
      - /api/blog/
      # user related api
      - /api/user/
      # others api
      - /
</code></pre>
<p>Access the metrics that nginxlog-exporter provided.<br>
We can know information:</br></p>
<ul>
<li>GET <a href="http://app.com/?ref=willis-tech">app.com</a> for <code>/api/blog/*</code> has been succeed for 55,984 times.</li>
</ul>
<pre><code class="language-bash">curl &quot;0.0.0.0:4040/metrics&quot;

dispatcher_nginx_http_response_count_total{app=&quot;nginx&quot;,environment=&quot;prod&quot;,host=&quot;api.com&quot;,method=&quot;GET&quot;,status=&quot;200&quot;,uri=&quot;/api/blog&quot;} 55984
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="finally-how-to-collect-these-metrics-and-evaluate-it">Finally, How to collect these metrics and evaluate it?</h3>
<p><img src="https://www.willischou.com/images/2023/03/notify.png" alt="FinTech - How to monitor API error rate?" loading="lazy"/></p>
<p>nginxlog-exporter expose metrics as Prometheus format, therefore, we use Prometheus to scrape it. Done! Now we have all the metrics in the time series database (Prometheus) &#x1F389;</p>
<p>We can add alert rule base on the requirement, for example an alert for 4xx error rate :</p>
<pre><code class="language-yaml">- alert: &quot;path /api/blog/ 4xx error rate exceed 1%&quot;
  expr: sum(increase(dispatcher_nginx_http_response_count_total{uri=&quot;/api/blog&quot;, status=~&quot;4..&quot;}[1m])) / sum(increase(dispatcher_nginx_http_response_count_total{uri=&quot;/api/blog&quot;}[1m])) &gt; 0.01
  for: 1m
  labels:
    severity: warning
    app: api
  annotations:
    description: &quot;Nginx blog related api (path /api/blog) 4xx error rate exceed 1%&quot;
</code></pre>
<p>This alert rule use <code>expr</code> to know how to evaluate the metrics with <code>PromQL</code> syntax.</p>
<p>Because the metrics we got <code>dispatcher_nginx_http_response_count_total</code> is an incremental integer, we need to use <code>increase</code> to find the different between two data points.</p>
<p>Since we want to get <code>4xx error rate &gt; 1% alert</code> here, we use <code>increase(4xx)/increase(all)&gt;0.01</code> &#x2728;</p>
<p>Also, we can use Grafana to create some dashboards.<br>
<img src="https://www.willischou.com/images/2023/03/---2022-11-27---10.43.27-redacted_dot_app.png" alt="FinTech - How to monitor API error rate?" loading="lazy"/></br></p>
<p>The End.</p>
<h2 id="future-plan">Future Plan?</h2>
<p>nginxlog-exporter can also aggregate the response time of request. If we want to monitor P99, P90 latency, we can try it!</p>
<p><strong><strong><a href="https://www.robustperception.io/how-does-a-prometheus-histogram-work?ref=willis-tech">How does a Prometheus Histogram work?</a></strong></strong></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[FinTech - EC2 deployment]]></title><description><![CDATA[Deploy EC2 based application with Blue/Green deployment.]]></description><link>https://www.willischou.com/fintech-ec2-deployment-nginx/</link><guid isPermaLink="false">Ghost__Post__63fb528383025200f28d53a3</guid><category><![CDATA[Ansible]]></category><category><![CDATA[GitHub Action]]></category><category><![CDATA[Typescript]]></category><category><![CDATA[Unit Test]]></category><category><![CDATA[Blue/Green Deployment]]></category><category><![CDATA[Prow]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:41:31 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/ec2-deployment-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="background-of-this-solution">Background of this solution</h2>
<ul>
<li>We need a method to deploy EC2 (VM) based applications safely.</li>
<li>I come up two approaches:
<ul>
<li>Progressive deployment which splits instances to many batches.</li>
<li>Blue/Green deployment which switch traffic between two Auto Scaling Groups.</li>
</ul>
</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="version-1progressive-deployment">Version 1 - progressive deployment</h2>
<h3 id="high-level-of-workflow">High level of workflow</h3>
<img src="https://www.willischou.com/images/2023/08/ec2-deployment-cover.jpg" alt="FinTech - EC2 deployment"/><p><img src="https://www.willischou.com/images/2023/02/deployment1.png" alt="FinTech - EC2 deployment" loading="lazy"/></p>
<p>Users submit  change via PR. After the PR was merged, the workflow analyze the change and find the corresponding Ansible Inventory. Then it separate the Ansible Inventory to multiple deployment groups by Auto Scaling Group.</p>
<p>Workflow deploys each deployment group parallelly. Within each deployment group, the workflow splits instances in the Auto Scaling Group to multiple batches. By doing so, we can deploy each batch with a configurable interval.</p>
<p>The flow looks like:</p>
<ol>
<li>get Auto Scaling Group in the specific environment.</li>
<li>get instances in the specific Auto Scaling Group.</li>
<li>split instances to multiple batches.</li>
<li>deploy each batch.</li>
</ol>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="how-to-use-this-workflow">How to use this workflow?</h3>
<ul>
<li>
<p>Users can use title to specify interval between each batches.<br>
If you specify <code>[interval: 300, 180, 60, 10]</code> in the PR title, workflow deploy your instances with 5 batches. It start with first batch and wait for 300 seconds, then it goes to second batch and wait for 180 seconds, and so on.</br></p>
</li>
<li>
<p>Users can add <code>[revert]</code> or <code>[hotfix]</code> to title, and it will skip progressive deployment in an emergency.</p>
</li>
<li>
<p>After merged to master branch, it triggers deployment</p>
</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="use-unit-test-to-ensure-the-workflow-result">Use unit test to ensure the workflow result</h3>
<p>I use JavaScript and TypeScript to implement most of the logic of the workflow.</p>
<p>By doing so, we can easily use <strong>jest</strong> to write unit test. High coverage is the key point for me to release new feature of the workflow with confidence.<br>
<img src="https://www.willischou.com/images/2023/02/workflow-result.png" alt="FinTech - EC2 deployment" loading="lazy"/></br></p>
<p>I use jest and mock <code>fs</code> , <code>child_process</code> to simulate the desired output for each test case. An example jest test file looks like:</p>
<pre><code class="language-jsx">describe(&apos;group_inventory&apos;, () =&gt; {
  // Because JavaScript in GitHub Action workflow uses environment to pass variables
  // we&apos;ll set environment variables for each test case too.
  const OLD_ENV = process.env;

  beforeEach(() =&gt; {
    jest.resetModules(); // clears the cache
    process.env = { ...OLD_ENV }; // Make a copy
  });

  afterAll(() =&gt; {
    process.env = OLD_ENV; // Restore old environment
  });

  // test case 1
  test(&apos;createInventoryGroup read a asg with 10 hosts, hosts per group is 10%,10%,20%,20%,40%&apos;, async () =&gt; {
    // set up
    jest.mock(&apos;fs&apos;, () =&gt; {
      ... truncated ...
    });
    jest.mock(&apos;child_process&apos;, () =&gt; {
      ... truncated ...
    });
    process.env.ANSIBLE_INVENTORY = &quot;inventory/aws_ec2.yml&quot;;
    process.env.ANSIBLE_HOSTS_PER_GROUP = &apos;10%,10%,20%,20%,40%&apos;;
    ... truncated ...

    // import
    // here is the function we want to test
    const group_inventory = require(&apos;../group_inventory&apos;);

    // invoke
    const resp = await group_inventory({ github: {}, context: {} });
    const shouldBe = [
      {
        asg_group: &apos;nginx_asg&apos;,
        asg_group_with_ranges: [
          &apos;nginx_asg[9:9]&apos;,
          &apos;nginx_asg[8:8]&apos;,
          &apos;nginx_asg[6:7]&apos;,
          &apos;nginx_asg[4:5]&apos;,
          &apos;nginx_asg[0:3]&apos;,
        ],
      },
    ];
    expect(resp).toStrictEqual(shouldBe);
  });
});
</code></pre>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="version-2bluegreen-deployment">Version 2 - Blue/Green deployment</h2>
<h3 id="high-level-of-workflow">High level of workflow</h3>
<p><img src="https://www.willischou.com/images/2023/03/nginx-blue-green.png" alt="FinTech - EC2 deployment" loading="lazy"/></p>
<p>After I implemented the version 1 workflow, I think the solution can be even better. For a EC2-based deployment, using Blue/Green deployment can provide the ability to rollback service within seconds. That&apos;s the reason I implemented version 2 workflow.</p>
<p>This workflow use Blue/Green deployment approach to deploy changes to instances.</p>
<p>We create one additional ASG for the existing ASG, and we group these two ASGs into one Blue/Green deployment group. Because every ASG has its own Target Group, we can control how many traffic go to each Target Group by weight of ALB rule.</p>
<p>This approach enhances the version 1 workflow with following perspectives:</p>
<ul>
<li>We can update EC2 AMI automatically.</li>
<li>We can rollback service within 1 minutes.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Project - E-Commerce]]></title><description><![CDATA[E-Commerce platform for a large online retailer.]]></description><link>https://www.willischou.com/side-project-matthew-e-commerce-keyword-php-laravel-angular-e-commerce/</link><guid isPermaLink="false">Ghost__Post__63fb520483025200f28d5386</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[Angular]]></category><category><![CDATA[E-Commerce]]></category><category><![CDATA[Dev Container]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:35:36 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/project-e-commerce-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="background-of-this-project">Background of this project</h2>
<ul>
<li>A Asia online retailer needs a discount system to meet their marketing team&apos;s requirements.</li>
<li>The discount system supports three types of promotions: A+B, total piece, and total price.</li>
<li>The discount system includes many functionalities like coupons, product groups, and shopping cart.</li>
</ul>
<h3 id="checkout-flow">Checkout Flow</h3>
<img src="https://www.willischou.com/images/2023/08/project-e-commerce-cover.jpg" alt="Project - E-Commerce"/><p><img src="https://www.willischou.com/images/2023/02/---2023-02-04---4.34.43.png" alt="Project - E-Commerce" loading="lazy"><br>
Users can browse products on the site.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/---2023-02-28---11.22.17.png" alt="Project - E-Commerce" loading="lazy"><br>
Once the user selected the products, he/she can view the price of each product after applied promotions.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/---2023-02-28---11.22.25.png" alt="Project - E-Commerce" loading="lazy"><br>
The user can select different payment methods and view the total price.</br></img></p>
<h3 id="coupons-management">Coupons management</h3>
<p><img src="https://www.willischou.com/images/2023/02/---2023-02-04---4.35.53.png" alt="Project - E-Commerce" loading="lazy"><br>
Users can manage their coupons in member center page.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/---2023-02-04---4.33.18.png" alt="Project - E-Commerce" loading="lazy"><br>
Staffs can manage the promotion rules in Admin portal.</br></img></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="what-i-did-in-this-project">What I did in this project</h2>
<ul>
<li>Used <strong>PHP and Larvael</strong> in the backend to implement E-Commerce platform functionailities.</li>
<li>Used <strong>Angular</strong> with <a href="https://akveo.github.io/nebular/docs/getting-started/what-is-nebular?ref=willis-tech#what-is-nebular">nebular</a> UI library.</li>
<li>Used <a href="https://code.visualstudio.com/docs/devcontainers/containers?ref=willis-tech">dev container</a> to build a delightful development environment locally.</li>
<li>Used Docker container to deploy services.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Project - Appointment match platform]]></title><description><![CDATA[Serverless backend in AWS.]]></description><link>https://www.willischou.com/side-project-appointment-match-platform/</link><guid isPermaLink="false">Ghost__Post__63fb51d483025200f28d5379</guid><category><![CDATA[AWS]]></category><category><![CDATA[Serverless]]></category><category><![CDATA[Aurora]]></category><category><![CDATA[Cognito]]></category><category><![CDATA[API Gateway]]></category><category><![CDATA[Lambda]]></category><category><![CDATA[Chime]]></category><category><![CDATA[GitHub Action]]></category><category><![CDATA[SES]]></category><category><![CDATA[AWS SAM]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:35:00 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/appointment-platform-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="background-of-this-project">Background of this project</h2>
<ul>
<li>I worked with friends to build a business appointment platform according to a Japanese team&#x2019;s requirement.</li>
<li>User can arrange meeting with other companies in this platform.</li>
<li>This platform aim to increase the visibility for registered companies.</li>
</ul>
<img src="https://www.willischou.com/images/2023/08/appointment-platform-cover.jpg" alt="Project - Appointment match platform"/><p><img src="https://www.willischou.com/images/2023/02/---2023-01-27---2.26.37.png" alt="Project - Appointment match platform" loading="lazy"><br>
User can browse company information.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/---2023-01-27---2.38.52.png" alt="Project - Appointment match platform" loading="lazy"><br>
User can arrange a meeting with each company.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/---2023-01-27---2.31.31-1.png" alt="Project - Appointment match platform" loading="lazy"><br>
User can manage received meeting invitations.</br></img></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h2 id="what-i-did-in-this-project">What I did in this project</h2>
<ul>
<li>
<p>The whole project is in <strong>serverless</strong> architecture. We used API Gateway, Lambda, Cognito, Aurora Serverless, SQS and SES to build the backend.</p>
</li>
<li>
<p>Used <strong>AWS SAM</strong> (CloudFormation) to deploy whole infrastructure.</p>
</li>
<li>
<p>Used <strong>GitHub Action</strong> to implement CI/CD.</p>
</li>
<li>
<p>Used <strong>Nodejs and Prisma</strong> to implement 50+ Lambda functions to fulfill every API requirements.</p>
</li>
<li>
<p>Use AWS SAM to deploy Lambda also has a build-in canary deployment functionality we can use.</p>
<pre><code class="language-json">...
Type: AWS::Serverless::Function
Properties:
  DeploymentPreference:
    # Start with 10 percent traffic to the new version. After 10 mins, it switches all traffic to the new one.
    Type: Canary10Percent10Minutes
</code></pre>
</li>
<li>
<p>Built an <strong>email notification microservice</strong> with SQS, Lambda and SES. Also, we used Event Bridge to generate daily report.</p>
<p><img src="https://www.willischou.com/images/2023/02/---2023-01-27---3.19.39.png" alt="Project - Appointment match platform" loading="lazy"/></p>
<p>After users registered, we send a welcome letter to each user.</p>
</li>
<li>
<p>Built an PPT to PDF converter with <a href="https://github.com/shelfio/aws-lambda-libreoffice?ref=willis-tech">aws-lambda-libreoffice</a>. User can upload their PPT and share it on this website.</p>
</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Project - Petio]]></title><description><![CDATA[iOS and Android apps for pet owners.]]></description><link>https://www.willischou.com/side-project-petio-pet-friendly-map/</link><guid isPermaLink="false">Ghost__Post__63fb512883025200f28d5358</guid><category><![CDATA[AKS]]></category><category><![CDATA[Azure DevOps]]></category><category><![CDATA[Nodejs]]></category><category><![CDATA[Nestjs]]></category><category><![CDATA[React Native]]></category><category><![CDATA[Angular]]></category><category><![CDATA[Terraform]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:34:19 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/pet-app-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="background-of-this-project">Background of this project</h2>
<ul>
<li>Recently, more and more people own pets. We provided an iOS and Android App for them.</li>
<li>You can use this App to search the nearest pet clinics and pet friendly restaurants.</li>
<li>You can use this App to report incidents on the map. For example, you can mark the position on the map when you find a stray dog.</li>
</ul>
<h2 id="what-i-did-in-this-project">What I did in this project</h2>
<ul>
<li>
<img src="https://www.willischou.com/images/2023/08/pet-app-cover.jpg" alt="Project - Petio"/><p>Used <strong>Nodejs with <a href="https://nestjs.com/?ref=willis-tech">NestJS</a></strong> framework to develop backend service.</p>
</li>
<li>
<p>Used <strong>Azure</strong> Blob, AKS, Azure Redis, MongoDB for our backend.</p>
</li>
<li>
<p>Used <strong>React Native</strong> to develop iOS App and Android.</p>
</li>
<li>
<p>Used <strong>Angular with <a href="https://ng.ant.design/docs/introduce/en?ref=willis-tech">ng-zorro</a></strong> (antd) framework to develop admin portal.</p>
</li>
<li>
<p>Built the whole infrastructure with <strong>Terraform</strong>. We can re-build all services to another Azure account within one hour.</p>
</li>
<li>
<p>Used <strong>Azure DevOps</strong> to implement CI/CD pipeline. In this side project, we spent 80% of our time in development.</p>
<p><img src="https://www.willischou.com/images/2023/02/azure-devops.png" alt="Project - Petio" loading="lazy"><br>
CI/CD in <a href="https://azure.microsoft.com/en-us/products/devops?ref=willis-tech">Azure DevOps</a>. Most of our services have unit test.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/azure-cd.png" alt="Project - Petio" loading="lazy"><br>
The CD includes 4 steps: dev infra &#x2192; dev k8s &#x2192; prod infra &#x2192; prod k8s.<br>
The pipeline applies infrastructure changes and application release.</br></br></img></p>
</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="features-and-screenshots">Features and screenshots</h3>
<p><img src="https://www.willischou.com/images/2023/02/petio1.jpeg" alt="Project - Petio" loading="lazy"><br>
User can report incidents with photo on the map.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/petio2.jpeg" alt="Project - Petio" loading="lazy"><br>
Map shows the incidents. Users can reply each incident and mark the incident as resolved.</br></img></p>
<p><img src="https://www.willischou.com/images/2023/02/petio3.jpeg" alt="Project - Petio" loading="lazy"><br>
Push notification to users when a new incident was reported around them.</br></img></p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Full Stack - Monitoring Platform and Chaos Engineering]]></title><description><![CDATA[Incident prevention platform with chaos engineering.]]></description><link>https://www.willischou.com/software-monitoring-platform-and-chaos-engineering/</link><guid isPermaLink="false">Ghost__Post__63fb50c183025200f28d5347</guid><category><![CDATA[AWS]]></category><category><![CDATA[Golang]]></category><category><![CDATA[React]]></category><category><![CDATA[TestCafe]]></category><category><![CDATA[gRPC]]></category><category><![CDATA[Chaos Engineering]]></category><category><![CDATA[Kinesis]]></category><category><![CDATA[Time Series Database]]></category><category><![CDATA[AIOps]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:31:24 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/chaos-engineering-platform-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="background-of-monitoring-platform-ipp">Background of monitoring platform (IPP)</h2>
<ul>
<li>Incident prevention platform (IPP) is a in-house monitoring and chaos engineering platform for our services.</li>
<li>It provides a UI portal to help user set up a daemon in their EC2 instances to collect logs and metrics.</li>
<li>It provides built-in dashboards to monitor EC2 resources, HTTP requests (Apdex) and AWS resources.</li>
<li>It provides chaos engineering functionality including CPU pressure, Memory pressure, Disk pressure and network connectivity blockade.</li>
</ul>
<h2 id="in-this-project-i-contributed-to-those-items">In this project, I contributed to those items</h2>
<ul>
<li>Introduced <strong>Terraform</strong> to the team and used it to deploy AWS infrastructure including Step Function, VPC Endpoint Service, Lambda, S3 Bucket, IAM Role.</li>
<li>Developed <strong>Step Function</strong> to help new customer to integrate with our platform. This Step Function created VPC Endpoint in customer AWS account and use SSM run command to set up Telegraf agent.</li>
<li>Developed backend service with <strong>Golang</strong> and <strong>gRPC</strong>.</li>
<li>Developed <strong>Lambda</strong> functions to aggregate data and interact with API by using gRPC.<br>
The aggregated data includes Apdex, P99, P50 to better monitor service performance.</br></li>
<li>Developed Grafana dashboard templating engine. Our backend service can create certain dashboards for customers based on their requirements.</li>
<li>Developed frontend with <strong>React</strong>. Users can execute a chaos engineering task by using UI.</li>
<li>Used <strong>TestCafe</strong> to write frontend E2E testing.</li>
<li>Implemented CI/CD in <strong>GitLab Pipeline</strong>.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="architecture">Architecture</h3>
<img src="https://www.willischou.com/images/2023/08/chaos-engineering-platform-cover.jpg" alt="Full Stack - Monitoring Platform and Chaos Engineering"/><p><img src="https://www.willischou.com/images/2023/03/IPP-monitoring-platform.png" alt="Full Stack - Monitoring Platform and Chaos Engineering" loading="lazy"/></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="logs-and-metrics">Logs and Metrics</h3>
<ul>
<li>Used <strong>InfluxDB</strong>, <strong>Prometheus</strong> and <strong>CloudWatch Metrics</strong> to store service metrics.</li>
<li>Used <strong>Elasticsearch</strong>, <strong>S3</strong> and <strong>Kinesis</strong> to store service logs.</li>
</ul>
<h3 id="chaos-engineering">Chaos Engineering</h3>
<ul>
<li>Used <code>tc</code> and <code>stress</code> to simulate packet lost, high network latency, CPU pressure and Memory Pressure &#x2026; etc.</li>
</ul>
<h3 id="eks-monitoring-solution">EKS Monitoring solution</h3>
<ul>
<li>Used Prometheus Operator to monitor services. Also, we used Prometheus Remote Write / Read to collect customer&#x2019;s metrics into our Prometheus. Then we used Prometheus Federation to aggregate data and provided user metrics from our aggregated Prometheus.</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[E-commerce Platform - staging env in EKS for 200+ stacks]]></title><description><![CDATA[Large staging environment in EKS.]]></description><link>https://www.willischou.com/ecommerce-platform-staging-env-in-eks-for-200-stacks/</link><guid isPermaLink="false">Ghost__Post__63fb501783025200f28d5329</guid><category><![CDATA[Kubed]]></category><category><![CDATA[External DNS]]></category><category><![CDATA[Cert Manager]]></category><category><![CDATA[Nginx Ingress Controller]]></category><category><![CDATA[Gomplate]]></category><category><![CDATA[Git-crypt]]></category><category><![CDATA[Samson]]></category><category><![CDATA[Bitbucket Pipeline]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:28:14 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/e-staging-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="whats-the-challenge">What&apos;s the challenge?</h2>
<ul>
<li>Staging environment needs 200 stacks.<br>
Each stack contains three services (api, storefront, admin)<br>
You can access stack 1 service by <code>api.stg1.service.com</code>, <code>shop.stg1.service.com</code> and <code>admin.stg1.service.com</code>.</br></br></li>
</ul>
<img src="https://www.willischou.com/images/2023/08/e-staging-cover.jpg" alt="E-commerce Platform - staging env in EKS for 200+ stacks"/><p><img src="https://www.willischou.com/images/2023/02/---2023-01-27---2.47.29-redacted_dot_app.png" alt="E-commerce Platform - staging env in EKS for 200+ stacks" loading="lazy"/></p>
<ul>
<li>200 stacks have many configurations and secrets need to manage.</li>
<li>Need a platform for DEV, PM and QA to deploy.</li>
<li>All services in staging must support TLS.</li>
</ul>
<h2 id="why-we-choose-eks-for-staging-env">Why we choose EKS for staging env?</h2>
<ul>
<li>We use EKS in our production environment. We need a staging environment in EKS too. By doing so, we can make sure our architectures of different environments are the same and testable.</li>
<li>50+ developers need to verify the result in staging environment parallelly. Using EKS can isolate the environment and benefit from Kubernetes community.</li>
</ul>
<h2 id="in-this-project-i-contributed-to-those-items">In this project, I contributed to those items</h2>
<ul>
<li>
<p>Built staging environment in EKS to host 200 stacks and all other e-commerce services (eg: open-api, third-party-api).</p>
</li>
<li>
<p>Created a document and hosted a training session for DEV, QA to teach them how to use EKS.<br>
After two months, most of our developers can work with EKS efficiently.</br></p>
</li>
<li>
<p>Used <strong>Gomplate (templating tool)</strong> to generate ConfigMap file for 200 stacks. By doing so, we only need to mange one file.<br>
For example, the following ConfigMap file can generate the ConfigMap file with desired <code>STACK_NO</code>:</br></p>
<pre><code class="language-yaml">metadata:
  name: custom-env
data:
  APP_CONFIG_HOST: api.stg{{.Env.STACK_NO}}.service.com
  APP_CONFIG_HOST: api.stg{{.Env.STACK_NO}}.service.com
</code></pre>
</li>
<li>
<p>Used External DNS, Cert Manger and Nginx Ingress Controller to serve all the staging service with HTTPS.</p>
</li>
<li>
<p>Used Kubed to duplicate TSL certificate within EKS cluster. Because we sign 100 domains in one certificate to reduce request count to Let&#x2019;s Encrypt.</p>
</li>
<li>
<p>Used Git-Ctypt to encrypt secret and commit to our version control. The concept is like <a href="https://github.com/bitnami-labs/sealed-secrets?ref=willis-tech">Sealed Secret</a>.</p>
</li>
<li>
<p>Set up <a href="https://github.com/zendesk/samson?ref=willis-tech">Samson</a> CI/CD platform for users to deploy services.</p>
</li>
<li>
<p>Our Staging cluster has 120+ worker nodes. We used different node type to fulfill different workloads. For example: System Node, Memory Node and CPU Node. By doing so, we can use computing resource efficiently.</p>
</li>
</ul>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[E-commerce Platform - MongoDB self-host to Atlas]]></title><description><![CDATA[In charge of high volume transactions MongoDB.]]></description><link>https://www.willischou.com/ec-platform-mongodb-self-host-to-atlas/</link><guid isPermaLink="false">Ghost__Post__63fb4fde83025200f28d531a</guid><category><![CDATA[MongoDB]]></category><category><![CDATA[RoR]]></category><category><![CDATA[Atlas]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:26:47 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/e-mongo-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="what-is-this-mongodb-cluster">What is this MongoDB cluster</h2>
<ul>
<li>Core database for the e-commerce platform.</li>
<li>Has 500,000 shop datas including orders, customers &#x2026; etc.</li>
<li>self-host in AWS EC2 with 3 shards, and each shard has 4 nodes (2 secondaries, 1 primary and 1 arbiter).</li>
</ul>
<h2 id="why-we-did-this">Why we did this</h2>
<ul>
<li>Moved our self-host MongoDB cluster to MongoDB Atlas (fully managed service).</li>
<li>MongoDB Atlas provides permission control and near real-time monitor.</li>
</ul>
<h2 id="in-this-project-i-contributed-to-those-items">In this project, I contributed to those items</h2>
<ul>
<li>Made a migration plan and completed the migration within three months.</li>
<li>Created 4TB cluster with 2 shards and 8 nodes (1 read-only&#x3001;2 secondary&#x3001;1 primary per shard).</li>
<li>Managed Atlas cluster with Terraform.</li>
<li>Upgraded MongoDB driver version and ruby version due to MongoDB upgrade.</li>
<li>Made a plan to restrict traffic during migration.</li>
<li>In charge of core MongoDB stability.</li>
<li>Sharded MongoDB collections to ensure high performance.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="architecture">Architecture</h3>
<img src="https://www.willischou.com/images/2023/08/e-mongo-cover.jpg" alt="E-commerce Platform - MongoDB self-host to Atlas"/><p><img src="https://www.willischou.com/images/2023/03/mongo_arch.png" alt="E-commerce Platform - MongoDB self-host to Atlas" loading="lazy"/></p>
<!--kg-card-end: markdown--><h2 id="what-benefits-mongodb-atlas-brings-to">What benefits MongoDB Atlas brings to?</h2><ul><li>Fine-grained Access Control: <br>You can control who can access your data and who can manage your cluster.</br></li><li>MongoDB Atlas Search: <br>Natively support text search functionality. you don&apos;t need to run a search engine anymore.</br></li><li>Monitoring: <br>Atlas provides near real-time monitoring dashboard. You can know what queries are running on the cluster.</br></li></ul>]]></content:encoded></item><item><title><![CDATA[E-commerce Platform - Data Pipelines with Debezium and monitoring]]></title><description><![CDATA[Data Pipeline implementations and monitors.]]></description><link>https://www.willischou.com/ecommerce-platform-data-pipeline-with-debezium-and-monitoring/</link><guid isPermaLink="false">Ghost__Post__63fb4f7e83025200f28d5305</guid><category><![CDATA[Prometheus]]></category><category><![CDATA[Debezium]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:25:54 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/e-data-pipeline-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h2 id="what-are-these-data-pipelines">What are these Data Pipelines</h2>
<ul>
<li>The analysis tasks of e-commerce platform rely on a Postgres database. We have a data pipeline to sync our data from MongoDB and MySQL to Postgres.</li>
<li>The data pipeline of MongoDB to Postgres was achieved by <a href="https://www.mongodb.com/docs/manual/changeStreams/?ref=willis-tech">Change Streams</a></li>
<li>The data pipeline of MySQL to Postgres was achieved by <a href="https://debezium.io/?ref=willis-tech">Debezium</a></li>
</ul>
<h2 id="why-we-did-this">Why we did this</h2>
<ul>
<li>We need to ensure these data pipelines are in-sync and functional.</li>
<li>We need to build a CDC solution to stream MySQL changes to Postgres.</li>
</ul>
<h2 id="in-this-project-i-contributed-to-those-items">In this project, I contributed to those items</h2>
<ul>
<li>
<img src="https://www.willischou.com/images/2023/08/e-data-pipeline-cover.jpg" alt="E-commerce Platform - Data Pipelines with Debezium and monitoring"/><p>Used Nodejs to implement a custom Prometheus Exporter to collect the latest timestamp of specific MongoDB Collection and get the latest timestamp of record we wrote to Postgres.</p>
</li>
<li>
<p>Used Prometheus and Grafana to build a dashboard and alerts for the data pipeline. Once the data in the Postgres behind MongoDB for more than 5 minutes, it sends out an alert.</p>
</li>
<li>
<p>Deployed Debezium in EKS to fulfill MySQL CDC requirement.</p>
</li>
<li>
<p>Developed a custom Helm Chart to help us add Service Monitor more easily.</p>
<pre><code class="language-yaml">{{- range $serviceMonitorName, $ref := .Values.serviceMonitors }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: {{ $serviceMonitorName }}
  labels:
    release: prometheus-operator
  {{- if hasKey $ref &quot;labels&quot; }}
    {{- range $key, $value := $ref.labels }}
    {{ $key }}: {{ $value | quote }}
    {{- end }}
  {{- end }}
spec:
  namespaceSelector:
    matchNames:
    {{- range $namespace := $ref.namespaceSelector }}
      - {{ $namespace }}
    {{- end }}
  selector:
    matchLabels:
      {{- range $key, $value := $ref.selector.matchLabels }}
      {{ $key }}: {{ $value | quote }}
      {{- end }}
  endpoints: {{- toYaml $ref.endpoints | nindent 4 }}
{{- end}}
</code></pre>
</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="architecture">Architecture</h3>
<p><img src="https://www.willischou.com/images/2023/03/Data-Pipeline-and-Monitoring.png" alt="E-commerce Platform - Data Pipelines with Debezium and monitoring" loading="lazy"/></p>
<p>The high level view of this solution.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="result">Result</h3>
<p><img src="https://www.willischou.com/images/2023/02/alert.png" alt="E-commerce Platform - Data Pipelines with Debezium and monitoring" loading="lazy"/></p>
<p>We can monitor the delay of data pipeline with near real-time.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[E-commerce Platform - Image Service GCP to AWS]]></title><description><![CDATA[The journey of migrating a image service from GCP to AWS.]]></description><link>https://www.willischou.com/image-service-gcp-to-aws/</link><guid isPermaLink="false">Ghost__Post__63fb4dec83025200f28d52c4</guid><category><![CDATA[AWS]]></category><category><![CDATA[GCP]]></category><category><![CDATA[Pub/Sub]]></category><category><![CDATA[Functions]]></category><category><![CDATA[GKE]]></category><category><![CDATA[Lambda]]></category><category><![CDATA[Lambda Layer]]></category><category><![CDATA[CloudFront]]></category><category><![CDATA[EKS]]></category><category><![CDATA[Elasticache Redis]]></category><category><![CDATA[Keda]]></category><category><![CDATA[Terraform]]></category><dc:creator><![CDATA[Willis]]></dc:creator><pubDate>Sun, 26 Feb 2023 12:24:00 GMT</pubDate><media:content url="https://www.willischou.com/images/2023/08/e-image-service-cover.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><h3 id="what-is-image-service">What is Image Service:</h3>
<img src="https://www.willischou.com/images/2023/08/e-image-service-cover.jpg" alt="E-commerce Platform - Image Service GCP to AWS"/><p>It&#x2019;s a asynchronous image resize service. When you access product image in the e-commerce platform, you&#x2019;ll access image with <code>https://image-service.com/image/1?size=100x200</code>. The service will try to find the image with id <code>1</code> and size <code>100x200</code>. If it can&#x2019;t find an existing one, it sends out a job to resize the image in the background.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="why-we-did-this">Why we did this:</h3>
<ul>
<li>Reduce the data transfer fee between two cloud providers.</li>
<li>Focus on AWS. As the team is mainly using AWS.</li>
<li>Improve manually renew certification with AWS Certificate Manager.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="in-this-project-i-contributed-to-those-items">In this project, I contributed to those items:</h3>
<ul>
<li>
<p>Moved architecture from <strong>GCP to AWS</strong> (<strong>GKE to EKS</strong>). Modified RoR application to use <strong>AWS Web Identity</strong> to get SQS permission and interact with <strong>SQS</strong> in EKS.</p>
</li>
<li>
<p>Moved GCP Cloud Function to AWS Lambda. This includes using <strong>Lambda Layer</strong> to store image processing library and implementing <strong>Lambda function</strong> to process image.</p>
</li>
<li>
<p>Solved Lambda faced limitation of disk space. Image Service uses disk to store temporary artifacts. However, the disk size has <code>/tmp</code> 250 MB limitation. Considering using <strong>EFS would cost greatly</strong>. Therefore, we Terraform to deploy multiple Lambda functions. Because <code>/tmp</code> <strong>disk only shared within same Lambda function</strong>.</p>
</li>
<li>
<p>Used <code>k8s-cloudwatch-adapter</code> to scale out/in our service. I replaced <code>cloudwatch-adapter</code> with <code>Keda</code> afterward. The auto scaling mechanism can handle workload with avg. 50k rpm and max. 80k rpm.</p>
</li>
<li>
<p>Optimized the CloudFront cache rule. In the beginning, our application rely on <code>User-Agent</code> Header to handle request. This can greatly impact cache. I changed our application to <strong>get the required data from Query String instead of Header</strong>. The result is increasing cache Hit Rate from 50% to 90%.</p>
</li>
<li>
<p>Optimized memory usage. I observed the application and find the application has Memory Fragmentation issue. Then, I change our Memory Allocator to <code>jemalloc</code> . By doing so, we reduced 80% memory usage.</p>
</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="architecture">Architecture</h3>
<p><img src="https://www.willischou.com/images/2023/03/Image-Service-GCP-to-AWS.png" alt="E-commerce Platform - Image Service GCP to AWS" loading="lazy"/></p>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><h3 id="result">Result</h3>
<p>After we disabled CloudFront Forward Header, and cache started to work.<br>
<img src="https://www.willischou.com/images/2023/02/image-1.png" alt="E-commerce Platform - Image Service GCP to AWS" loading="lazy"/></br></p>
<p>After we changed the Memory Allocator, memory usage decreased by 80%.<br>
<img src="https://www.willischou.com/images/2023/02/---2022-02-02---11.14.46.png" alt="E-commerce Platform - Image Service GCP to AWS" loading="lazy"/></br></p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>