Member-only story
Decoupling a core service from your monolith the right way
Our monolith problem
WeTransfer, like many other products, started as a small monolithic application and quickly became a big monolithic with too many responsibilities and contributors. It was getting harder to ship new features, and the technical debt was growing. That’s why we started decoupling some core logic into different services. One of those modules was the billing logic.
The billing module in charge of users’ payments and subscriptions is one of the core services of our business. A lot changed since our first implementation, and since everything lived in the same monolithic, the billing codebase was coupled with other core modules like transfers and authorization.
This post will describe our different steps to decouple all the billing logic without significantly impacting the 80M+ active users using our products. There are many different ways to approach this project, but hopefully, our learnings will help you plan yours.
Phase 1: Decouple billing logic within the monolith
Our billing logic was very coupled to the rest of our monolith, and it took a lot of effort to understand the interface between other modules and the billing codebase. So our first approach was to decouple the billing logic within the monolith. We did that in two parts:
- We moved all our billing classes into new folders and modules. So most of our billing classes were prefixed with
Billing::
(since the codebase is in Ruby). Then we set our team as the GitHub code owner of those folders so we would be notified of any contributions made. We also took this opportunity to communicate with the rest of the engineering team about our plans and that any new logic using billing modules should be carefully reviewed. For example, we didn’t want to add more dependencies between the existing modules and the billing logic. - We created different
Client
classes as middleware interfaces between the billing code we wanted to migrate and the rest of the monolith. The idea was that every interaction with the billing logic would need to go through one of these Clients. For example:
Before the decoupling:
