I am currently working on the architecture of a new project for my company. This new project must be compatible with an historical monolith, then extend it. It’s a common case to rewrite an existing application with modern toolkit.
What is the Strangler Fit Pattern?
Martin Fowler was describing in a article back in 2004 a way to slowly replace a monolith by a new product: the Strangler Fig Pattern. The main advantage: a slow migration, one endpoint by one endpoint, not a bigbang. This pattern is actually widely used when refactoring an application to micro-services for example.
From Microsoft Learn
Why in SST (Serverless Stack)?
SST is a serverless framework that help you to build fullstack application. On my new project, I want to take advantage of this framework (it’s a classic piece of our toolkit nowadays!), but I want to replace an application, not to create a new one. So the Strangler Fit Pattern seems pretty attracting.
Architecture
Based on a AWS reInvent presentation (Youtube video link here), I was convinced to use an API Gateway with the ANY /{proxy+}
to facade the application:
From AWS Presentation, Migrating monolithic applications with the strangler pattern
Starting from there I have tried to simulate a monolith application (represented by a ApplicationLoadBalancedFargateService
CDK construct in my project). The implementation of the monolith is not very important, I just rely on the Application Load Balancer (there is 99% chance you have such service if your monolith is hosted on AWS, either on EC2 or ECS!).
Here is my monolith in CDK:
const loadBalancedFargateService = new ApplicationLoadBalancedFargateService(
stack,
"Service",
{
propagateTags: PropagatedTagSource.SERVICE,
memoryLimitMiB: 1024,
desiredCount: 1,
cpu: 512,
taskImageOptions: {
image: ContainerImage.fromRegistry("nginxdemos/hello"),
},
publicLoadBalancer: false,
}
);
For the demo, it’s using a very simple docker image, nginxdemos/hello
Then SST is allowing me to use the same {proxy+}
in the API routes:
const api = new Api(stack, "api", {
routes: {
"GET /": "functions/lambda.handler",
"ANY /{proxy+}": {
type: "alb",
cdk: {
albListener: loadBalancedFargateService.listener,
},
},
},
});
And voilà, I can build new routes with SST, and all routes not specified in routes
will be handled by the old application declared in the ApplicationLoadBalancedFargateService
.
The route /
is answered by my lambda.handler
function:
Any other routes are answered by my monolith:
To Conclude
This was a quick one but I think it can be a very efficient way to move slowly to SST framework! You can find the example repository on GitHub here. Happy tests with this pattern on modern stack, or chat with me on Twitter!