background

As systems age, development tools become outdated, and bugs accumulate, projects become harder to maintain. So “decay” is an inevitable part of all legacy projects. Legacy projects are rarely left out of business, but many modern companies like to refactor their code every two or three years with new technology. Legacy code is notoriously hard to change, so where does their confidence come from?

The story begins with an old man named Martin Fowler. Once, while traveling in the rainforest, he stumbled upon a plant called the “Strangler vine” :

The strangler vine grows all the way up the other tree trunks to get light; As the vine continues to grow, the previously parasitic tree will become completely covered by the plant and die as its sunlight resources run out; The tree rots away, but a huge, tree-like vine is left in place.

Martin went back and wrote about the story on his blog, and he came up with a strategy called the “Strangler pattern” : gradually building a new application by refactoring individual applications (rather than pushing them back). This became the basic guideline for developers to modernize the heritage system.

Strangler mode

After talking about plants, let’s discuss “how to strangle” from the implementation strategy; The general operation of strangulation is as follows:

  1. Create a facade to intercept requests from backend legacy systems
  2. Under certain rules, the facade routes specific requests to the old and new systems separately — reverse proxies
  3. Retain the original functions of the legacy system, while rewriting the old modules in the new system, and gradually tilt the requests into the new system; During the migration process, due to the isolation of the facade, consumers use the existing functionality as usual without any sense of background refactoring
  4. After the migration is complete, all requests are routed to the new system and the legacy system is “hanged”

The advantages of the strangler are: the code of the legacy system is preserved, in the form of a smooth migration towards new applications; This ensures that every step forward has an opportunity to go back. Gradual migration may take a long time, and may even preserve some of the legacy system’s functionality permanently.

In field

OK, the theory is simple, but in practice there are all sorts of problems, and the refactoring process can take months or even years. Just claiming that “system migration can be achieved through strangler mode” is useless: the thought that you are going to move all the code immediately panics, and decision-makers (tech-savvy or not) are not going to act so hastily. So it’s better to have a more detailed breakdown step. The so-called “split” can start from the presentation layer, service layer, persistence layer and so on. Just remember to start with the easy part, and as the value becomes apparent, you can keep accelerating.

In general, the presentation layer is the easiest to split, such as JSP or Tymeleaf, a back-end rendering framework, into restful API + React form — activity separated — and quickly shipping. Let’s look at the process.

The reverse proxy

The first step in implementing the Strangler mode is to add a facade. The simplest facade I can think of is the professional reverse proxy tool Nginx. I’ve written about reverse proxies before, and you can check them out here. Make sure that team members have a basic knowledge of redirection; The first step to cognitive impairment is very frustrating.

In general, the first step of the reverse proxy does not require any processing of the request, but simply penetrates:

If you use Nginx, the configuration is as simple as proxy the root route to the legacy system service:

# nginx.confserver { location / { proxy_pass https://legacy.com; . }}Copy the code

Transfer function

Once the HTTP reverse proxy is in place, we can begin to extract the functional code. Of course, there are many migration strategies, such as separating the presentation layer, refactoring the database, extracting domain services and so on; Due to space constraints, this issue will only talk about the simplest separation of performance layers.

Take the more primitive JSP application as an example, usually can do the JSP “dynamic separation” refactoring:

  • The dynamic part: the model data that was originally bound to the JSP. Expose them as Rest apis, leaving the business logic under the JSPS untouched
  • The static part: the HTML template (UI) part of the JSP. This part is reimplemented with a modern front-end framework such as reactJS. The rewritten UI code is all put into the new system, leaving the JSP code in the legacy system untouched

As an aside, after using the new technology stack to realize the functional module, the corresponding CI/CD should also follow in the first time; A series of UI tests should also be written starting with the first line of code. Otherwise, after a few months, you will find that the new system is not much stronger than the old one.

redirect

If CI is properly configured, the React code changes from Merge PR to completing the new system module update — which is actually just static files at this stage — should be manageable in a matter of minutes.

How will the new system integrate? We need to redirect specific resources requested by the browser to the new system:

The new system needs a new agent routing mechanism (such as /modern/) to distinguish it from the old system. Add location to the agent configuration:

# nginx.conf

location /modern/ {
  ...
  proxy_pass http://modern.com/;
}
Copy the code

Of course, the new system still won’t work. The reason for this is simple: at the beginning of the refactoring, the HTML entry was mostly in the legacy system, and the UI would not be associated with the new system unless there were hard code /modern/ related requests in the code — which is not something we want to see.

What to do? Here is a trick to use nginx to inject js pointing to modern to all HTML. The configuration looks like this:

# nginx.conf

location / {
  proxy_pass https://legacy.com;
  sub_filter '</body>' '<script type="module" src="/modern/app.react.js"></script></body>';
  sub_filter_once on;
}
Copy the code

By injecting app.React. Js into the legacy HTML, all UI-related actions can be modified in the new system code. The Legacy System JSP does not need to be changed.

Data binding

As mentioned above, “dynamic separation”, how can data and template be bound to each other after separation? In case you’re wondering, React.js usually takes the form of an asynchronous request Rest API to get the data and implements the binding on its own template. This is known as the MVVM pattern and is a big reason why modern front-end frameworks are superior to JSP frameworks.

Let’s take a look at the order of page requests after refactoring with React:

  1. Browser request page
  2. Because of nGINx’s default Settings, requests are routed to legacy systems
  3. The JSP generates the HTML and returns the page
  4. Nginx returns the injection script tag to all HTML<script type="module" src="/modern/app.react.js"></script>
  5. After the browser parses to the above tags, it initiates again/modern/The associated JS request
  6. The request will be routed to the new system
  7. New system returnapp.react.js
  8. The browser executes the js above, finds a Rest API request, and initiates a data request
  9. Data requests also take the default configuration route and are directed to the legacy system by Nginx
  10. The legacy system returns JSON data from the Rest API

Finally, the React framework automatically implements data binding in the browser. Compared to JSP, which renders all the time in the back end and returns a huge HTML, the “dynamic separation” mode is a bit more complicated in the process, and may cost a little more cognition for newcomers. But when it comes down to the code, it’s actually simpler; This is something that you can feel once you’ve written a page or two, and I’m not going to go into it.

summary

OK, the basic framework of UI refactoring is basically set up, and then it is time to gradually migrate each front-end module according to the specific business. It is recommended that react be used as jQuery in the initial phase — that is, when lib is used: find the specific DOM of the heritage code by selector and refactor it; Once a page is complete, modify nginx.conf to hijack the new page to the Modern system. If things go well, the refactoring will show up after the first page is finished — a visible loading speed.

After the front-end migration, is the back-end service split, I will be in the “Strangler mode (2)” further explain, please pay attention to!

Broken words SuiYu

I once worked on a legacy project with a single architecture that accumulated tens of thousands of bugs over the years. However, the average number of bugfixes per developer per week is only 1 (and a few new bugs are introduced from time to time). I couldn’t think of any way to reduce the number of bugs on my books before the project was completely finished, so I made a special trip to the factory to ask for instructions from a leading cadre. He told me, “Redefine the bug, and the bug is gone!”

After hearing this, it suddenly becomes clear that there are never enough bugs to fix, so don’t worry about fixing one or two bugs per week. Take a few minutes — say 20% of the workforce — to move the product, work out the business logic along the way, and when the migration is complete, the legacy bugs are no longer bugs.