Conf42 Cloud Native 2022 - Online

Journey from monolith to microservices on AWS

Video size:

Abstract

Lifting and shifting applications into the cloud, even monolithic apps, requires minimal to no changes to the application architecture and is straight forward. Once rehosted in the cloud, modernizing a monolith into a microservices based architecture is a natural next step. It helps teams with agility but requires a methodical approach to minimize risk of disruptions to the business.

In this session, I cover how to modernize a monolithic application into a microservices-based architecture in AWS. I apply the strangler fig pattern using AWS Migration Hub Refactor Spaces to chip away at the monolith iteratively and demonstrate step by step how to move a sample Spring Boot Java application into microservices built on AWS serverless components.

The session is targeted for cloud teams who are looking to modernize their monolithic application stacks rehosted on AWS including those in regulated industries where security and reliability are critical such as Financial Services and Healthcare.

Summary

  • Lernak McJolo is a senior solutions architect with AWS. He will discuss how to move from monolithic applications towards microservices. In a practical example with a demo, we will be chipping away at the monolith using the strangler fig pattern.
  • Monolithic applications need to be deployed in its entirety together with all of its components. Over time, what you may see is the challenges of an overgrown monolith. One of the challenges is a list of feature requests that we are not able to deliver in a timely manner. Another challenge is frequency of releases.
  • Microservices are minimal function services and they're each organized around business capabilities. You can deploy each one independently and each can scale independently as well. Each microservice is focused on a specific business function with a single responsibility. How do we evolve the overgrown monolith?
  • Refactor spaces lets you get cross account visibility into the other accounts that are hosting the other services, such as the monolithic application and the shopping cart service. The recommendation is to use an account per app for isolation of resources.
  • One of the major benefits of moving towards microservices architecture is that we can develop each microservice using different technology stacks that is most suitable for the use case. Refactor spaces is enabling us to follow best practices in having account level isolation for these services.
  • We will be creating refactor spaces environments. In reality, in real world, you would likely first have the monolith and then add the new microservices as you create them. Best practice is to have multiple accounts, one account per application per environment. Let's dive into our app modernizing workshop.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Hi everyone, I'm Lernak McJolo. I'm a senior solutions architect with AWS. I have 18 years background in technology and financial services industry. I started with centralized web infrastructure such as authentication systems, single sign on federated authentication. I also work with high performance distributed caching as well as multilegion cloud native deployments using infrastructure, AWS code and pipelines to name a few. And in those specific talk I'm using to go over why and how to move from monolithic applications towards microservices in a practical example with a demo, and we are going to be chipping away at the monolith using the strangler fig pattern. So let's jump in. So first of all, why would we want to do this? So if you think about monolithic applications, what we are referring to is an application that needs to be deployed in its entirety together with all of its components. And we have multiple layers inside such an application, such as presentation, application logic, data access layer, and the application has one database for the entire app. You have one technology stack for the entire app, and also it may be running as a single process. You may have many developers that are all pushing changes through a shared release pipeline, and that may cause frictions at different points in software development lifecycle. For example, if you were to want to upgrade a shared library to take advantage of a new feature, you need to convince everybody else such that they upgrade at the same time. And if you were to quickly push an important fix for a feature, you still need to merge everybody else's changes alongside yours. So over time, what you may see is the challenges of an overgrown monolith. So what are those challenges? Let's take a look at that. So one of the challenges is a list of feature requests that we are not able to deliver in a timely manner to our users. So we have long lead time to delivering features. Another challenge is frequency of releases where we have releases maybe a couple of times in a year, and because we're not deploying as frequently to production, each release contain a lot of changes for the entire application. That then creates a high risk deployment. We have many changes bundled together. We're not really exercising our muscle to deploy to production because we're not releasing to production enough. So we have high risk deployments, but also if we run into any issues during the release, we also have high risk rollbacks. So you may typically see in this scenario teams prefer to fix forward, which is pretty high risk as well, but it's easier. So I've been in a situation worked in a team where I faced each one of these challenges. So I know of the pain of each one of these challenges from personal experience. So you may now ask, how about microservices? So let's take a look at the microservices. What are they? So microservices are minimal function services and they're each organized around business capabilities. You can deploy each one independently and each can scale independently as well. Each has its own independent technology stack and its own data store. They can talk to each other over APIs and you can have different teams aligned to each microservice such that the teams independently architect, design, develop, deploy, maintain each of these microservices. So all of that is good, but how does all of these things tie in together? So we need to evolve this monolith. It has the challenges that we just talked about. Now, when we talk about evolving the overgrown monolith, we are not thinking about a big bang approach because that just is not realistic. Instead, what we need to do is an iterative incremental approach. We can explore different new technologies and refactor components from this monolith where we are looking to gain some speed, to gain some agility, or we're looking to be able to independently scale specifically to meet the demands of the business. So we are iterating modernizing incrementally for a business purpose. So this is where we are going to start. Now, why should we refactor the service for business value? Independent deployment is the heart of microservices and with that we are going to gain reduction of the blessed radius for deployments. Each microservice is focused on a specific business function with a single responsibility and the teams aligned to a microservice can decide their own architecture and their own tech stack. This brings functional autonomy. With that we have minimized the dependencies across and that will maximize the development velocity for the teams working on the microservice. Now with each service focused on a single responsibility, we have the opportunity to focus scaling the high demand functions as opposed to scaling the whole app in its entirety, including the lesser used parts, lesser used functions alongside it. So let's take a look at our sample bonnet. We have an ecommerce shop. It's called the Unicorn shop. This is where we are selling unicorn related items such as a unicorn pencil, unicorn notebook and the application is a spring boot Java application. It has a MySQL database and on the front end it is using bootstrap. The application is deployed on EC two within a dedicated VPC in a public subnet for the purposes of this sample demo. And this is basically the tech stack for the entire monolithic ecommerce shop application. So our monolithic application has a number of controllers on top. So you see the inventory management in the unicorn controller. You see the shopping cart or the basket management in the basket controller, and the user controller that deals with user management with registrations and logins. We also have a health controller that performs basic health checks that I didn't include. Over there at the bottom, we have the database tables. We have each one focused on a specific domain. So we have three tables. We have the unicorns table. That is our unicorn teams inventory all the way on the right. We have the users table. Those are the users in our system. And in those middle, we have an association table that associates the users and their selections of unicorn items in their basket. So how do we go about evolving this monolith? We will use the Strangler fig pattern that's named by Martin Fowler from Thoughtworks and alongside him Sam Newman, previous thoughtworker. Both have really useful content on the topic. The idea is that our new services, the microservices, are similar to the strangler fig that's slowly growing around the trunk of the old tree, the system, the monolithic application. Now, if we go back to the sample in our ecommerce application, those basket functionality is critical and it needs to be highly available, durable and scalable to meet the ongoing sales demand, as well as spiky demand for sales such as Black Friday surges. So one good first step to evolve the ecommerce app is to move the basket functionality that's critical to the business out of the monolith and allow it to scale independently. So when thinking about breaking a monolith, we need to consider a few points. What is the best technology stack that is going to help us to implement the new microservice? And what would scale best based on the usage of this, in this case, shopping cart service, the basket service. There is existing data in the tables in the MySQL database. So how do we move the data from the database of the monolith to the database of the new microservice? There will be internal data exchange between monolith, microservices, AWS, the monolith. So when we are breaking those monolith to smaller chunks, we are introducing those chatter to the network. So we need to be thinking about the performance impact of that. How do we enable seamless switchover between the monolith and microservices? How do we make sure that the current customers, current consumers of our APIs won't break. So let's focus on this. How can we seamlessly chip away at the monolith? First of all, there is an existing service contract. That's a REstful API that is exposed by our monolithic application, and we need to abide by this contract such that we're not breaking any of our existing application consumers existing clients. So how do we achieve that? First of all, what's in this restful API? For example, if you want to create a user, you'll be showing an HTTP post to user Uripad. If you wanted to log in, that's a post HTTP post to user login Uripad to get all of the inventory, all of the unicorns, such that we can show it in the homepage. It's a get HTTP, get on unicorns and so on. So we are interested in replacing the monolith's implementation of the shopping cart functionality with the new microservice and all of the shopping cart. The based related API actions like adding, removing items from the basket, getting the contents of the basket, they're all at a specific URI pad, so they're hanging off of the base URL, unicorns and basket. So that's those URI pad. So we will first introduce a reverse proxy in between the apps consumers and the monolith. And for now, the reverse proxy is taking the requests from the consumers, the application consumers, and routing them all to the monolith. We can then introduce the refactored microservice behind the reverse proxy, and we will configure it to route the requests mapping to the unicorn's basket URI Pat such that it all goes. Those requests go to the refactored shopping cart microservice. So one way to achieve this already today, like on AWS, is to use AWS API gateway. API Gateway allows you to define resources and methods. So first you would create all of the unicorn resources, and then you would add methods on top of that. You would configure, create and configure HTTP methods on top of those. But is there a faster and less manual approach? We can leverage AWS migration hub refactor spaces to accelerate the evolution of our monolith. It is going to help us with managing the iterative refactoring process while operating in production using the strangler fig pattern. This way we can focus on developing the applications for the new microservices and not lose time and effort in creating and managing those underlying infrastructure that makes refactoring possible. So let's dive into how refactoring the monolith looks like using those approach. So let's walk through the steps for also going to be used for our demo of refactor spaces. First you are going to create the refactor spaces environment in an account that becomes the environment owner. This account is special because it is going to get cross account visibility into the other accounts that are hosting the other services, such as the monolithic application and the shopping cart service. Now, refactor spaces configures transit gateway in this account on our behalf when we create the environment, and because the transit gateway is in our own account, we can customize it as needed. Next we are going to add the other accounts to be used for refactoring to the environment. So in this example we have two accounts. One is the existing monolith account that's already available and then the other is for the new microservice. So in real world you likely have the monolith app first. So you would be adding that account first and eventually when you create the shopping cart service or the second microservice for example, then you would add that later. Now in our demo we are actually using one account for both of these, but as a best practice in terms of following the multi account strategy, the recommendation is to use an account per app for isolation of resources, which I'll dive into in a little bit. Next we are going to create an application proxy in the environment owner account. Remember that reverse proxy? So that's what we are doing here. And AWS part of this refactor spaces on our behalf is configuring the API gateway VPC link and network load balancer such that it can allow external HTTP access to the services in our refactor spaces environment. So the monolithic application and also the shopping cart service in those example. Next we are going to add a service with its VPC or you can also create this service from the specific service account. For example from the shopping cart account you could create the service and refactor spaces configures the transit gateway to bridge the vpcs. So traffic is now permitted between the service vpcs in each of the accounts. So the monolith application VPC and the shopping Cart VPC now have cross account network bridge and now we are ready to add a route for the external access to our application. So we will likely first add the default route to send all of the application traffic to the monolith. And all traffic initially is using to services by the monolith and API gateway is just acting as our front door to the application right now. And then once the monolith microservices AWS ready, it's available, we will route a specific set of the URI pads to the monolith microservices AWS external app users don't know that traffic is now being handled by the new shopping cart microservice for the shopping cart requires so also want to deep dive into the architecture here. One of the major benefits of moving towards microservices architecture is that we can develop each microservice using different technology stacks that is most suitable for the use case. So here we decided to use lambda as the compute and Dynamodb as a database for the shopping cart functionality given their ability to run and scale based on usage with high availability. Think about Black Friday or Cyber Monday. So our unicorn shop is not going to have any issues with scaling for those spiky sales days in this architecture. And Lambda and DynamoDB are serverless. That means that we don't need to worry about provisioning and managing servers. We can focus on application development for this shopping cart application and also we will only pay for what we use now another thing that I had mentioned is that refactor spaces is enabling us to follow best practices in having account level isolation for these services. So we're following the multi account strategy for each application here, monolith app and the shopping cart app we have a separate account and this is helping us in multiple ways. We are going to minimize the blessed radius with this approach. If there is an issue in one of these accounts with one of these apps that is scoped to the account in which the application is, we are isolating resources that belong together based on perhaps their security profile or data profile. For example, different business units, different environments will have different security profiles, different data access profiles and we are able to isolate those resources if we use separate accounts. We are also ensuring that each workload gets a well defined individual quota for its resource limits because we are using a separate account. So here we're using refactor spaces. It is orchestrating the networking and traffic routing requirements for us. It's creating that network fabric that we talked about earlier with the reverse proxy. And this way the monolith and the new microservice are able to communicate directly across different AWS accounts and our users are not impacted of any changes that are happening as we are refactoring with chip innovate this monolith. So let's dive into our app modernizing workshop. So here is the workshop link. If you'd like to follow along in your own account, in your free accounts. And of course, after you're done with the workshop, make sure to clean up your resources. So, brief overview of the steps we just already discussed. So it's the same steps. We will be creating refactor spaces environments. We will be adding the accounts. In our case, we only have one account, but best practice is to have multiple accounts, one account per application per environment. Ideally we will create the app proxy in refactor spaces. We are not creating these resources individually. Refactor spaces is taking care of the creation of the API gateway Transit gateway VPC link network load balancer. Underneath the covers we'll be creating defining those services, the monolithic application and the shopping cart service. In reality, in real world, you would likely first have the monolith and then you would add the new microservices as you create them. And then we'll be adding the routes such that we can route the appropriate requests based on the URI pads to the appropriate service that is in changes of those requests. So in this case, Unicorn's basket URi pad is going to be routed to the shopping cart microservice, the new one and everything else. The default route for all those other requests will be routed to the monolithic app. So let's jump into our demo. So as a first step, we create those refactor spaces environments. Here at the bottom you see a brand new one that was created and when we do this, what happens is that transit gateway is also created on our behalf by refactor spaces. After this step, if we have our monolithic application in a different account, then we can share this refactor spaces environment to that account. We can also do that once we chip away at the monolith. If we have our new microservice in a different account, we can do the same. We can share those refactor spaces environment to the microservice account. For the purposes of the workshop. We have a simple use case, so we have all of the services in the same account. But as a best practice, you already know that in real world you'll be following multi account strategy. After you create the environment, the next step is to create the application. And when you create the application, it will also create all of the application proxy resources. Remember that's the API gateway, the VPC link, the network load balancer, and this way you are going to have a proxy endpoint URL that is going to be used by the front end, in this case for the unicorn shop, the front end that is bootstrap JS deployed in public facing s. Those bucket is going to be using that proxy endpoint URL as its destination as soon as we put the monolith behind that proxy endpoint URL. So here you're seeing the front end, the application that we'll be creating in refactor spaces. And first we are going to configure the service, the monolith service behind it. We are going to route all of those requests by default to the monolith. So that's what you would typically do as well in the real world. And then AWS, you apply the strangler fig pattern to chip away at the monolith. In the next stage, you would add another service and you would define all of the routes to that service. In our case, that's a shopping cart. So I'll be going over those routes and how we would configure that as well in a minute. So as you can see, as part of creation of the application, we are telling it where to put the proxy VPC and where to deploy those resources, which is the API gateway reST API, the VPC links network load balancer and the resource policies resources. So here I already have an application inside this environment, refactor spaces environment. That's the unistore app that already has the API gateway endpoint. It has the network load balancer that it created on its own. I didn't have to create those on my own. So that is what refactor spaces is giving me. I don't need to focus on creating all the network layer resources that are required to make refactoring easy. And after I'm done creating the application, then I came here and I created those services. First I added the monolith. I named it Monolith and I gave it the endpoint, which is this is the EC two URL endpoint from inside those VPC. And after that, once I chipped away at the monolith and I created three lambdas, using the workshop to add teams to the shopping cart, to remove items from the shopping cart, and to get teams from the shopping cart. Each one of these is a separate lambda. Remember with the DynamoDB database for those services. Here is how I'm doing the routing. So first of all, it is Pat based URI pattern match. So unicorns basket. That's the pattern match on the Uri pad. And also it's coupled with HTTP verbs because this is a restful API to get the items from the cart we're using HTTP get. So that is mapping to the get cart service lambda to delete teams from the cart we are using HTTP delete and that is mapping to the remove cart service lambda. So any requires that come in as HTTP deletes on those unicorns basket is going to be routed to this lambda and also addition of items to the cart using to get routed to the add to cart service lambda. So it's as easy as that. I'm able to come here in the future if I want to add another microservice. If I decided that over time that there's a need to chip away further to this monolith, I create that microservice in a different account, I'll share my refactor spaces environment to that account and I'll define the service inside this application, the uni store unicorn shop application inside this environment. And then I will also define the routes, those Uri pads coupled with the HTTP verbs that I'm going to use to route to this new microservice. And over time you can iteratively, incrementally chip away at that monolith using refactor spaces without having to worry boot the network load balancer, API, gateway, VPC link Transit gateway, all that configuration because it is all taken care on your behalf. So here is how that unicorn shop looks like this is using the monolith for everything but the shopping cart. So if I'm doing actions inside the shopping cart, then those actions are going to my lambda. But if I am actually doing other things here, if I am browsing, clicking on other things, if I'm logging in, et cetera, then those are going to the monolithic app. So with that concludes those demo. So here are our key takeaways from this talk refactor. When it provides business value, iterate incrementally. If it still provides business value, use an app proxy such that you honor the service contract to the users so that all of these changes are happening seamlessly to them and follow multi account strategy. For any new microservices that you're chipping away from that monolith min minimize that blessed radius and all the good things that come with multi account strategy benefit from them. Thank you.
...

Lerna Ekmekcioglu

Senior Solutions Architect @ AWS

Lerna Ekmekcioglu's LinkedIn account Lerna Ekmekcioglu's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways