Why Serverless

Over past 8 also years I have been working as Solution Architect in various financial institutions in Toronto area such as BMO, TD, and now IGM. I am focusing more and more on application architecture, system design, and most importantly system integration among various layers through Micro Services and APIs, Kafka and event queues, and quite often batch jobs too, while writing less and less codes by myself. Now a lot of time I work on legacy technologies such as mainframe and Ab Initio stuff alongside some other cloud projects like Microsoft Azure, GCP and AWS. One day one of my friends in my soccer team (Yes I am still playing soccer at the age of almost 50 years old) asked me some questions about Amazon Serverless that I couldn't answer at all. My hands were so itchy that I decided to convert one of my hobby projects to Servless architecture.

I have an Amazon arm64 t4g.medium EC2 instance running with

  • Apache server to serve the Angular SPA application
  • 6 Springboot MicroServices
  • MongoDB as NoSQL database
  • Kafka to glue these 6 MicroServices together through Event Driven Integration pattern
I know some of the technologies especially Kafka I chosen here are overkill given the volume to this application is very low. However this is the way I learn and experience technologies at my free time. When time is right and the organization starts to use the technologies here, I can recommend the technologies and design patterns to utilize the advantages while avoid shortcomings of them. I really don't want to repeat the Data Persistent Bean error I learned many years ago that should not be used in the real life project at all.

This EC2 instance costs me around $30 a month. It is not much however given the extreme low volume, it is a perfect scenario for me to convert it to Servless architecture to experience new technologies and cut the cost 50% or even more. Since it is going to be Serverless Architecture, I need to consider 2 major technical challenges that will impact the decisions.

The first challenge I need to solve is application start up time. To be fair, I am very happy that my Java Springboot Rest Service can boot up in 6 seconds with MongoDB connections and Kafka consumer subscription. I can still remember how happy I was when my web application could start in 20 seconds when we switched to IBM WebSphere Liberty Profile server in 2015. Before that I always complained it took so much time to start a WebSphere instance and how fast it was to start a WebLogic server which took 1 or 2 minutes to start up if I remember correctly. There was one time we need to wait more than 30 minutes to shutdown a WebSphere portal and then brought it up after the new codes deployed. But in Serverless world, 6 seconds is too slow to serve the first client call if application needs to cold start even though the real REST call took only let's say 50 millisecond. This problem will be magnified and my application will be almost not-usable at all since the daily volume is so low.

			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [NestFactory] Starting Nest application...
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [a] d dependencies initialized +103ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [d] U {xxx}: +81ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, POST} route +3ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +0ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +0ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +0ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, GET} route +0ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, PUT} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, PUT} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, PUT} route +0ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, POST} route +0ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, POST} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, POST} route +0ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [A] Mapped {xxx, DELETE} route +1ms
			[Nest] 12035  - 2022-02-01, 1:55:26 p.m.     LOG [v] Nest application successfully started +2ms
			HTTPS Service started on port 8081 in 259 ms
        
By converting to a Node.js REST service, the application start up time is cut to only 259 ms. Looks pretty good.

The second challenge I want to solve is the memory footprint. The average RAM my Springboot MicroService uses is between 256MB and 512MB. I cannot complain much in Java world since I was so used to at least 1GB or 2GB allocation to one JVM in the old time. But I am really curious how much memory a Node.js service uses. My smallest Node.js REST service is using 26MB, while biggest one is using 168MB as I built a cache there to improve the response performance.

After converting my first Springboot service to Node.js service. I get more confident on Node.js technology stack. I am serious about moving to the Servless architecture. Here is the project plan:

  1. Convert all my 6 Springboot MicroServices to Node.js based services. The technology stacks are:
    • NestJS framework for REST service controllers and business services. NestJS is chosen over other Express frameworks because it is heavily influenced by Angular. TypeScript is my go to language to develop Javascript applications because I a firm believer on the compile time type checking. I want the errors show up immediately when I am writing codes, refactoring my codes (Yes I do a lot of refacoring!). I don't want my application crash or behave weirdly during runtime because of a typo. I never really understand how a pure javascript project can be done efficiently by a large group of developers if a strong type language is not used. TypeScript filled this gap and it is the goto language for developing Javascript applications. NestJS have a lot of other nice features like annotation based controllers for HTTP request routing, Angular like service/component injection, and AOP concept based Interceptors and Guards etc.
    • Mongoose for MongoDB integration. I did not spend too much time to compare TypeORM against Mongoose, I simply chose it because it is the most popular MongoDB object modeling tool according to NestJS web site. Obviously I follow the NestJS Mongo document and use NestJS decorators/annotations to create Schema, Document type, and then inject them into my business service layer. If you are familiar with Spring Data MongoDB, you can think @Schema() of Mongoose equivalent to @Document() of Spring. Then the Mongoose Model injected to the service is equivalent to the Spring MongoRepository interface. It has quite a few different find(), save(), and findByIdAndDelete() methods which allows you to perform normal CRUD against the database. The syntax of find() queries is pretty much same as MongoDB native scripts that you don't need to translate to Spring Data MongoDB syntax which is easier in this case.
    • kafkajs for Kafka integration. Since my plan is to replace Kafka with Amazon SQS in the serverless architecture, I encapsulated all kafka related Producer and Consumer codes into one KafkaUtil class.
  2. Then I will replace kafka with Amazon SQS. This should be done quickly since all my Kafka codes are encapsulated in one KafkaUtil class. At that time I could reduce the EC2 instance from t4g.medium to t4g.small to cut 50% of my cost.
  3. Finally I will convert all my MicroServices to AWS Lambda sererless architecture, and move MongoDB to SaaS model - MongoDB Atlas

Since it is a hobby project that I will work on it in the evenings and weekends besides the time spent on playing soccer and watch England Premier League and European Champions League, it will be another 3 to 6 months project with quite a lot of time will be spent rewrite the Java codes with TypeScript codes. This time I decide to document all the fun I will have and all the challenges and technical difficulties I will solve, and learned lessons in this whole process so that eventually I can come back to conclude how easy or how hard for a Java guy with 20 years experiences to convert to Javascript/TypeScript Node.js technology stack and Serverless architecture.