Open edx is a great open-source learning management system, but it's also an all-inclusive monolith. As such, there are some significant drawbacks to using this platform for your project needs. One of the biggest pain points comes when making small changes like changing text on landing pages or updating the logo can take a good amount of the developer's time due to needing code modifications and recompiling assets from servers with every little change made in place.
In this article, we'll be looking at how we decoupled the Open edX frontend by using Strapi and Nextjs and brought the joy back to our developers.
Before we start, let's answer some key questions to make sure we're on the right track.
What is a monolith system?
A monolithic system is a software system with one code base and all the functionality included.
Why monolith system is not ideal architecture anymore?
In recent years, with the rise of microservices and containerization, having a monolith system can turn into a nightmare for software development teams. For example, in the case of open edx we have a python/Django app in which all features are stuffed together. We never know what would break when refactoring some parts of the application or adding new ones.
If one piece breaks, we need to wait for the full or partial system to be deployed again via Ansible. This can take hours, even days if a big migration is involved.
How does a monolith system influence developers' productivity?
A monolithic application will slow down the development process. We already mentioned that deploying the system can take hours or days. In this case, every change in the code takes a lot of time and requires more effort than usual to be deployed on production servers.
On top of all this, if we want to release a new feature, it is not possible to test it in a sandbox environment and roll back if we encounter any issue. We have to wait for the deployment process to finish (compile assets, migrations, deploy, etc.)
How could we decouple the monolith?
Decoupling is when you separate an application into two or more parts that don't depend on each other. Each part has its own codebase
What is a decoupled system?
A system architecture is said to be "decoupled" if each of its components may evolve independently of the others. The term decoupling generally refers to the removal of direct dependencies between two modules or components. In this sense, "backend" and "frontend" are loosely coupled.
In our case, we're trying to decouple the Frontend from the backend part of Open edx.
The biggest benefit, you get by doing it, is The ability to update your front-end without touching the Server-side code. With this method, development becomes way faster and easier for core developers. When a new frontend developer joins the team, he or she can start working with minimum onboarding.
What does this mean for my users?
A decoupled system distributes the work of developing a web app between your client-side and server teams. The net result is that you have a faster feedback loop when building features. It also allows you to change the look and feel of your app without needing to rebuild it each time.
What do we gain by decoupling ?
The decoupled architecture can give you a few advantages:
Better design, better onboarding & maintenance for front-end developers
Developers are no longer blocked by slow backend development cycles
Faster shipping and iteration time overall.
Easily scale the application by dividing it into multiple microservices.
What do we need to do to decouple a monolith?
- Choose a client-side framework like Nextjs
- Create APIs with Strapi
- Build APIs in your Backend if needed
- Use these APIs to build your Frontend
What we lose by decoupling a monolith?
There are some downsides to this approach:
It requires more initial work to set up the front-end and APIs, as well as understanding how they will interact with each other on a technical level
Teams must be cross-trained to maintain API quality and consistency
It can take time to identify which parts of your monolith are safe to decouple
Ok, enough with technical details, let's go through a quick example. As an example, I will show you how the frontend development looks before and after decoupling, we also take a quick look at the site performance.
Open edX before decoupling
As I mentioned earlier, the project we are working on called open edx, which is an open-source learning management system. When you install it, you got something like the following out of the box. I know it doesn't look too good, and the worst part comes when you want to start working on changes on UI. Having a development environment means having the monolith up and running in your local machine. The minimum hardware requirement is at least 4G-6G of RAM to have smooth development environment.
Let's make a simple change in frontend
Ok, let's say we want to replace the logo placeholder with our actual logo. Here are the steps you need to follow:
- Make change in your code repo
- Pull them in the server
- Run python paver command to compile assets (takes around 10-30 minutes)
- Restart manually LMS services
- wait for 2-5 minutes till the services are up and running
- Check if the change applied to your site
- If you don't see your change, repeat step 3 to 6 again
This took longer than what you see in the video, I needed to cut the video to make it lighter to upload :)
How the performance looks like
Here is the lighthouse report Looks pretty bad :(
Open edX decoupled
Now let's go to the fun part. If you are curious how we decoupled openedx theming, we used existing APIs like courses APIs and implemented new ones like registration and enrollment. The reason behind that is we want to create some records in Strapi automatically based on what we have in Open edX to keep our LMS as a single source of truth. So for example, our Next.js frontend makes a call to open edx courses API and create or update courses in Strapi, so we don't need to manually create them there, and we have a single source of truth.
We created all the frontend elements as content type in Strapi, we have for example navbar, hero, footer, course cards, tutors content types in Strapi. You can even change site color globally from Strapi with one click
In Nextjs we have all of our React components, like hero, navbar, course etc. they are styled with Tailwindcss. Our site is pre-rendered using SSG and hosted on Vercel to bring blazing fast experience to our users. In our Nextjs app we have a GraphQL client that makes calls to Strapi, fetch data and during the build time we construct components based on what we receive from Strapi.
How does it look like ? Beautiful
How is the performance ? Fast
Let's change the logo again
Also, the Vercel build time was pretty quick. 1m1s
By decoupling the frontend, we could integrate our platform with Auth0 and Stripe pretty easy. If we wanted to do this with previous design, it would take longer time for development as well as be more expensive to maintain because of how often open edx updates its source code on a regular basis. Fortunately, since these integrations live in our frontend now, all that's required is making sure the APIs we talked about earlier, still work. Something much easier than maintaining compatibility across different systems!
Decoupling your monolith can bring a new life to your team. It allows faster iteration, less coupling between backend and frontend, better scalability and maintainability of the system. All this without slowing down the development process of adding new features for your users. We found Strapi and Nextjs a great mix to achieve decoupling for our use cases and greatly contribute to the maintainability, scalability, and performance of the frontend application.