Transitioning to Polylith

How much will Polylith affect our current codebases and deployment experience?

Migrating to Polylith from a Monolith, Microservices or Serverless architecture is relatively easy. That's because we can individually migrate each artefact to a Polylith service without changing anything else about our deployment.

Let's say that we have twelve Microservices in our current solution. After we complete the initial stage of a migration to Polylith, then we'll still have twelve Microservices, but each will be a Polylith service.

Let's take a look at the steps involved in transitioning from each type of architecture.

From a Monolith

Don't forget to check that the project compiles, builds and all tests pass after each step.

1. Create a new workspace, and add a new project, with an empty base. 2. Copy all your Monolith's code, including its API into the base and add all the libraries to the project.

3. Extract all the code from the base (except the API) into a single "monolithic" component:

It's tempting to do a lot of refactoring during this first component extraction phase, but we'd advise against that. Instead, we should just move all our code into a single component, and change as little as possible about its structure. This ensures that we don't introduce any bugs, and gives us a known and stable state to continue from.

4. This is where the real fun starts, because now we can refactor the code to increase the modularity of the project. We start by teasing out one component at a time from our "monolithic" component:

When we've finished extracting all the components, we'll have a project that's in much better shape:

Some components will handle a specific part of our domain, some might manage integration with external systems, and others will be responsible for infrastructure features such as logging or persistence.

From Microservices

Microservices is an architecture consisting of many small Monoliths. This means that migrating to Polylith is as simple as performing the Monolith migration steps on each service:

It's tempting to try merging components across service boundaries as soon as we notice similarities. We'd advise against this because during the transition we want every Polylith Microservice to behave exactly the same as before. This ensures that we don't introduce any bugs and gives us a known and stable state to continue from.

Once the initial migration of all our microservices to Polyliths is complete, then we can start to refactor.

It's likely that there are a number of common components that can be shared across mulitple services. Resuing components in this way, make our codebase DRY and easier to maintain.

We might also discover that we had prematurely optimized our Microservice architecture for scalability and/or single responsibility. In other words, we have more services than we actually need to achieve the scalability we require.

Those additional services come with a hefty complexity cost, so we'll be able to make our life much simpler by combining them into fewer Polylith projects. Whilst still maintaining the architectural benefits of separating our code into single responsibility components.

From Serverless

Serverless is an architecture consisting of many Lambda functions. This means that migrating to Polylith is as simple as performing the Monolith migration steps on each Lambda:

As with Microservices, we are not forced to migrate all our Lambdas at the same time. If we have many Lambdas this is especially good news, because it allows us to migrate in small and controlled steps.

Defrost our libraries

If we have extracted shared functionality into internal libraries that we maintain, then Polylith gives us the opportunity to defrost them into living code. Libraries are created by freezing code in time, which leads to friction in the development experience. By defrosting them into components, we get living code that is easy to change and which is always in sync with the rest of our codebase.