This pattern is part of "Patterns of Legacy Displacement"
Extract Product Lines
Identify and separate systems by product line.
21 July 2021
Ian Cartwright, Rob Horn, & James Lewis
Many applications are built to serve multiple logical products from the same physical system. Often this is driven by a desire for reuse. "Hmm, consumer loans look quite similar to business loans" or "clothes are a product, so are made-to-measure curtains, how different can they be?". A major problem we come across is that superficially the products look similar but they very different when it comes to the detail.
Over time, single systems serving multiple products can become over generic, with code evolving to handle all possible combinations of all possible products. For example, for a generic system that is designed to handle n products with n changes per product the amount of testing that must be done to check all possible combinations is n factorial. That's a number that gets big quickly. It also explains why many of the applications of this type that the authors have encountered have had very little in the way of automated test coverage, instead relying on huge, often manual regression suites. It's simply not possible to test so many different codepaths.
The problem is often therefore one of economics. It is difficult to accept that developing and maintaining a system per product might make better economic sense than developing and maintaining a single system. By splitting by product we are taking advantage of the fact that changes to multiple products can be made simultaneuously, and reducing risk by avoiding the combinatorial explosion of changes that may introduce defects in unwanted places.
Of course this is a trade-off - we don't want separate applications for red trousers versus black trousers, but we may want one for off-the-peg versus made-to-measure, or Home insurance vs Pet insurance.
It is also common for different product lines to have very differerent value streams as described in Extract Value Streams.
How it Works
Identify the product or product lines within the system. This will form the thin slice to be built / migrated. Look to identify all the capabilities that the existing system provides and map them to the new product. We tend to look through different lenses when identifying capabilites, data, process, users etc.
Identify shared capabilities. Identify if the different product lines have BusinessCapabilities that are shared. There are several ways of approaching this, which we cover in UnderstandingYourBusinessCapabilities. Our advise as ever is to value Use over Reuse, so if in doubt try and limit the number of shared capabilites as much as possible
Choose who goes first. Which product lines are you going to move off first? One approach we like is to think in terms of risk. After understanding the risk to the business of migration, we often like to take *the second riskiest product line*. This may feel counter-intuative and that we should take the least risky option first but actually we like to identify a product that is meaty enough to keep the business' attention and cause them to prioritise funding, but not so risky that the business will fail if there are problems.
Identify your target software architecture. It is very rare that we advise big bang replacements, which in this case would mean building out all the software for all of the products at once. Instead, look to identify the appropriate architecture for the thin slice identified in step 1.
Identify your technical migration strategy. As we discuss in the section on technology migration patterns, there are a number of different options that can be deployed depending on current constraints. If it's a simple web-application then it may be possible to use ForkByUrl. In other cases where ForkingOnIngress is appropriate it may be better to choose the MessageRouter pattern. Keep in mind that a TransitionalArchitecture may be necessary.
When to Use It
You have a system with easily identifiable product lines that would benefit from:
- Being worked on independently. Breaking up the system into separate products means that teams can be formed around the individual products allowing for progress to be made without the typical problems of change co-ordination including merge hell and long regression cycles.
- That have different non-functional characteristics. You want to be able to offer different SLO's for each product. For example different load requirements for a given latency.
- Different rates of change. Some product lines are stable while other products are undergoing active development. Breaking up the system means that you do not risk changes to the stable products.
In the insurance domain, different product types have very different characteristics. For example Vehicle insurance is typically high volume but low margin, whereas Home insurance is the opposite. It is also highly competative so the ability to make changes quickly is very important. One insurance company we worked with had developed a 3-tier architecture that served as the quoting engine for all the different products the insurer offered including Vehicle, Life, Home and Pet lines as shown in figure 1.
Understand the outcomes you want to achieve
The product owners for the business were increasingly frustrated at the lead time to change which was long and getting longer. They decided to get Thoughtworks in to take a look at their architecture and development processes to see if we could identify why things were so bad. The value stream map for the development process identified a number of constraints that were contributing to lead times exploding. While each technical domain was decoupled from the others, the different business domains were tightly coupled together. This meant adding a new requirement for the Vehicle product would often impact Home, Life and so on. These changes equired both careful thought and extensive regression testing before the usually fraught deployments. The multi-product architecture also imposed a limit on the number of people able to work on the codebase safely, further slowing progress.
Finally, due to the volume requirements of the Vehicle product line and the growth of the business, the underlying data store had to be scaled often, requiring downtime to all the other products hosted by the system.
Decide how to break the problem up into smaller parts
As a result, the insurer decided to migrate away from their n-tier architecture organised around technical capabilities towards one organised by product lines. The products were already identified: Vehicle, Home, Life and Pet insurance. Once these were understood the different capabilities that they each needed were identified, for example the individual question sets, quoting and customer account as well as more technical capabilities such as Authentication and Authorisation. Customer Account was identified as a key shared capabilty that all of the product lines would use, a good example of adopting the Coordination approach from the EA Magic Quadrant which is described in more detail in The One True Ring.
The next thing that needed to happen was to identify where to start. In order of revenue to the company, the product lines rated as Vehicle, Home, Life and Pet. Whilst in terms of customer numbers the order was reversed. As a result it was decided that Home insurance would be the first product line implemented separately. This balanced risk to the business of something going wrong with enough revenue to make the choice important.
Successfully deliver the parts
The above figure shows the event-driven architecture that the team started to build out. Communcation with the Quoting Engine and with the Customer Management capabilities was via events passed over a RabbitMQ message bus. These events were also propagated to the existing enterprise data warehouse for reporting purposes.
As the team built out the new systems to the side of the existing one, preparations were put in place to cut over traffic from the legacy system to the new one. One downside to moving the product line in it's entirety was that the question set had to be implemented in full before customers could be switched over. Due to this contraint the new system went into a Beta phase where certain customers were offered the option to opt-in to use the beta version. Those that opted in also had the opportunity to provide feedback on the new look and feel. As the new system was progressively enhanced and the final cosmetic features were added, a go-nogo decision was taken and customers were gradually redirected to the new system over the course of several weeks. First one percent, then five, then ten percent etc. This allowed the team and business to grow in confidence that the new system was performing as expected, both from a functional and non-functional point of view. Finally, the new system was serving one hundred percent of traffic for Home Insurance. The team then turned to ongoing product development.
Change the organization to allow this to happen on an ongoing basis
After the success of the first migration, attention turned to the next challenge, moving Vehicle insurance using the same approach and the pattern was repeated until the migration was complete and the old system turned off.
Meanwhile, and gradually, the whole technical organisation moved away from a project-based approach to development to a product-centric one. This of course had teething problems. Product Ownership is a skill that needs to be built over time and so the transition was a gradual one. They also adopted the same approach to traditional IT Operations, and under the guidance of the CTO and Chief Architect moved towards a platform product engineering approach for on-demand infrastructure and then data and analytics.