“Don’t put all your eggs in one basket” – while legacy architecture designs have helped us serve award-winning apps for our clients back in the day, change was slow and deployments and maintenance tasks were always a headache requiring too much planning, effort and overhead. So, how did we improve on this?
At Concentrix Catalyst, our inquisitive nature brings insight and innovation. And migrating our hosting services over from the local datacenter to AWS back in 2013-2014 made it even easier and better to innovate and keep up with the latest best practices. With AWS, you’re always presented with new services, solutions and opportunities for improvement.
Shared dependencies between applications bring shared risks; i.e. each application introduces its own risks to a bigger pool of risks shared by all applications.
So, without further ado, here are some of the approaches we’ve taken to decouple and de-risk the applications we build and manage wherever and whenever possible:
Decouple Your Multiple Applications on a Shared Infrastructure
This is pretty much decoupling 101. If you have multiple applications hosted on the same infrastructure, move them away from each other. The less they share, the fewer risks you would have. So, here are some infrastructure decoupling practices:
If your applications are managed through one repository, split them. Creating new repos should be easy and straightforward with any provider, especially if you are hosting your own source code service, but look into CodeCommit if you’re looking for a reliable and cheap managed repo service that you can integrate so easily with other AWS services and developer tools.
If your applications share CI/CD pipelines, separate them. CodePipeline makes it very easy to create pipelines and standardize them via CloudFormation templates. Also, the sky is your limit with CodeBuild and CodeDeploy. Check out my previous post “10 smart way to use CodeBuild” for ideas.
If your applications are hosted on the same instance or same set of instances, separate them and deploy on different instances or different sets of instances. Use Launch Templates and Auto Scaling Groups, Elastic Beanstalk or OpsWorks to manage your instances and their configurations. Also, make use of Elastic Load Balancing to distribute traffic between your instances and to add an extra layer of protection in front of them. And once they’re up and running, make use of AWS Compute Optimizer or Trusted Advisor Cost Optimization checks to increase efficiency and avoid unnecessary costs. One big instance is not always cheaper nor always better than two smaller instances.
If your applications share a database instance or a set of database instances, separate them onto their own instances or own sets of database instances. Without a doubt, RDS is the best for this. With RDS, you can manage backups and read replicas easily, scale your instances vertically or horizontally in minutes, and you can migrate easily to RDS with the Database Migration Service.
Decouple the Frontend and Backend of Your Full Stack Applications
Another easy one, perhaps, that every Software Engineer with experience would agree with. Simply, split your application’s Frontend hosting from your Backend hosting. They should be hosted separately so when one is down or undergoing maintenance, the other is still functioning. Obviously, the Frontend relies on the Backend to function but you want the Frontend to still work and show “nice” error messages rather than crash when the Backend is not available. Treat them as two different applications with separate repos, separate CI/CD pipelines and independent hostings as much as possible. A simple “we’ll be back soon” or “try again in 5 minutes” in the Frontend gives a very different experience to the end user compared to a blue screen of death when there’s a memory, database or deployment issue in the backend!
You can use S3 for your static websites and contents storage, CloudFront for your CDN, ECS for your Frontend or Backend containers, API Gateway and Lambda functions for your backend APIs and processors, or EC2 for anything you want to lift and shift as is.
Categorize and Decouple Your Infrastructure as Code Resources
Like many early adopters of IaC, our very early templates were either written per AWS service, which makes them too many, or for everything that makes up an application, which makes them too complicated. Imagine managing a template for VPCs, a template for Subnets, a template for RDS, a template for Load Balancers, etc. Too many. On the other hand, imagine managing one ~3000 lines template that includes everything for that application such as VPC, Subnets, RDS instances, Load Balancers, EC2 instances, IAM Roles, etc. Too complicated.
A few solid projects later, we found the sweet spot. We now group resources based on their logical service within the system we’re building. For example:
Infrastructure templates: the templates where we can manage the networking (e.g. VPC & Subnets) and security resources (e.g. IAM Roles). We can also add the resources that support-but-do-not-directly-host the application in this template if they’re not lengthy (e.g. S3 Buckets or Cognito User Pools). And, if the resources fit into the support-but-do-not-directly-host the application but they’re lengthy (e.g. RDS or Redis), we can add them to the infrastructure templates as Nested Stacks (for CloudFormation) or as Modules (for Terraform). These templates should be hosted in an “Infrastructure” repository and deployed through an “Infrastructure” CI/CD pipeline. Also, these templates should be parameterized allowing us to deploy them multiple times to create however many environments we need (Dev, UAT, etc) consistently.
Application templates: the templates where we manage the resources that host one application directly e.g. API Gateway and Lambda Functions, ECS Services and Tasks or Launch Templates and Autoscaling Groups. These templates should be hosted in their respective application’s repository, managed with the application and deployed through the application’s CI/CD pipeline. This gives the application’s team ownership and accountability over their application. A good DevOps practice. These templates are also parameterised and deployed for the number of environments needed.
Supporting templates: these are the templates where we manage shared resources, account level resources, CI/CD resources, etc. These templates are usually deployed once per account or once per application and managed alongside the infrastructure templates.
Finally, Refactor and Decouple Your Monolithic Applications
Perhaps, this is the first thing people think of when you start talking about decoupling; and this is where they give up. Decoupling monolithic apps could be overwhelming, time consuming and expensive when you look at it altogether! But, in reality, it doesn’t have to be. As you read so far, we have gone through different examples of decoupling and de-risking different things around the app. “Take the small wins to achieve the bigger goals”, and when you get to this stage, here are a few pointers to help you:
For backend apps: Look into your application’s backend and dig into the functionalities and services. If you have scheduled processes or backend tasks move them onto their own ECS containers or, better, Lambda functions and use a messaging service to invoke them such as SQS, SNS or EventBridge. Then, for the core services, rearchitect them in a Microservices architecture. The Microservices architecture could be made up of containers on ECS, Kubernetes on EKS, functions on Lambda or a mix of all. So many options and all of them are right. The Microservices could be as big or as small as you want them. The idea is to decouple what you can.
For frontend apps: This is relatively new but Frontends are now following the steps of backends as modern web apps have grown. There are so many services on AWS to help you decouple your Frontend but look into containers on ECS or static contents websites on S3. Also, take a look at the Micro Frontends architecture for more ideas.
For databases: I won’t bother you talking about refactoring and splitting databases. You can actually reduce the risks when you’re hosting multiple databases by simply moving them away from each other as mentioned earlier. If they’re on one big instance, move them to smaller suitable instances. If you have a master and replicas on the same host, move them away to different hosts. Better yet, move to a managed database service where you’re not relying on physical hosts nor on one or two hosts for management. Check out RDS High Availability and %99.95 SLA.
That’s all folks! I know I highlighted a lot of decoupling practices and mentioned a dozen of AWS services and examples, in a 5 minutes read so I will leave you with that. The aim is to inspire anyone out there looking for ideas and to show how easy it is to get started.