The Distributed SQL Blog

Thoughts on distributed databases, open source, and cloud native

Retail Application Migration: Lessons Learned Moving from On-Prem to Cloud Native

Recently, I came across a sample e-commerce application that demonstrates how to use Next.js, GraphQL engine, PostgreSQL, and a few other frameworks to build a modern web application. The application supports basic e-commerce capabilities such as product inventory and order management, recommendation system, and checkout function. This made me curious as to how much effort it would take to complete a retail application migration from an on-premise to cloud native solution.

The original architecture for this sample app looked like the below diagram. You can start the whole setup in a few minutes following this guide.

Sample retail application migration.

My curiosity was so tempting that I forked the project and replaced several components from the original architecture with their cloud native counterparts:

Sample retail application migration with YugabyteDB

The migration didn’t finish overnight but was relatively smooth. Let’s quickly walk through my three main lessons learned.

Lesson 1: PostgreSQL compatibility matters a lot

In a cloud native deployment, I want to have a relational database that scales horizontally and is resilient to failures such as zone- or region-level outages. Even though YugabyteDB is a PostgreSQL-compliant database, I was still skeptical that the retail application migration would go smoothly with no code changes on the SQL side. My skepticism grew even more after seeing that the original DDL script had many stored procedures and triggers that are pretty hard to support in a distributed database.

However, my skepticism turned into joy when the application booted with no issues after I introduced two changes to the Docker compose startup script:

And that’s it! That’s PostgreSQL compatibility in action. The application was running, the data was loaded, and the frontend worked with no issues. So, I prepared a separate docker compose file for those who would like to launch the app on-premises with YugabyteDB. I also moved closer to my original goal – turning the app into a cloud native solution.

Lesson 2: Cloud native services are easy to switch to

This lesson might be just an obvious fact to most of you. But for me, this was a big positive lesson.

To remind you, I intended to transform the application to a cloud native solution by replacing the following components with their cloud native counterparts:

So, starting with Step 1, I provisioned a 3-node cluster with Yugabyte Cloud. The cluster is deployed across several availability zones and can tolerate zone-level outages:

Switching to cloud native services.

Next, moving to Step 2, I created a Hasura Cloud deployment using the Standard Tier (that scales automatically and supports high availability). I also connected Hasura to my Yugabyte Cloud database:

Switching to cloud native services.

Lastly, finishing with Step 3, I deployed the Next.js backend and frontend with Vercel. I did this through the Environment Variables setting to connect to my Hasura Cloud GraphQL engine:

Switching to cloud native services in a sample retail application migration.

That’s it! As a result, my e-commerce application was running on a cloud native stack. The switch to these services was an easy thing:

Switching to cloud native services in a retail application migration.

Lesson 3: Zero code changes are a myth

As you see, the migration was smooth. Most of my time was spent adjusting configuration settings and provisioning cloud native services. But I cannot say this was a “zero-code-changes” type of experience. Personally, I think that that’s just a myth. You still have to be ready to tweak your code to make it work properly or much more efficiently with new software or services.

In my case, I had to change the Next.js logic in a few places, making sure Hasura Cloud’s admin secret is used correctly by the GraphQL clients. I also had to optimize the SQL scripts the app used for initial data loading. For instance, the original SQL script loaded 85,000 products by executing 85,000 individual INSERT statements that translated to 85,000 distributed transactions in YugabyteDB! But that’s just an anti-pattern for distributed databases. Therefore, I dramatically reduced the initial loading time by replacing those 85,000 individual INSERTs/transactions with ~17 INSERTs/transactions with 5,000 values each.


Instead of closing the article with some clever statements and high-level guidance, I would encourage you to experience this retail application migration process from start to finish (except for the code-level changes, that’s done for you!):

If you follow these steps, then the application UI will look as follows:

Sample retail application.

Got questions? Join the YugabyteDB community Slack channel for a deeper discussion with over 5,500 developers, engineers, and architects.

Related Posts