这是用户在 2024-11-1 9:54 为 https://martinfowler.com/articles/strangler-fig-mobile-apps.html#DivingDeeperx2026 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

Using the Strangler Fig with Mobile Apps
将 Strangler Fig 与移动应用程序结合使用

A case study of gradually modernizing an established mobile application
逐步对已建立的移动应用程序进行现代化的案例研究

Incremental replacement of a legacy mobile application is a challenging concept to articulate and execute. However, we believe by making the investment in the pre-requisites of legacy modernization, it is posible to yield benefits in the long term. This article explores the Strangler Fig pattern and how it can be applied to mobile applications. We chart the journey of an enterprise who refused to accept the high cost and risk associated with a full rewrite of their mobile application. By incrementally developing their new mobile app alongside a modular architecture, they were able to achieve significant uplifts in their delivery metrics.
传统移动应用程序的增量替换是一个具有挑战性的概念,难以阐明和执行。但是,我们相信,通过对遗留现代化的先决条件进行投资,有可能产生长期收益。本文探讨了 Strangler Fig 模式以及如何将其应用于移动应用程序。我们描绘了一家企业的旅程,该企业拒绝接受与完全重写其移动应用程序相关的高成本和风险。通过逐步开发新的移动应用程序以及模块化架构,他们能够显著提高交付指标。

30 October 2024 30 十月 2024


Photo of Matthew Foster

Matthew is a Technical Principal at Thoughtworks. Over his 15 years as a technologist, Matthew has spent his time building solutions and leading teams for businesses both large and small across retail, energy, telecommunications and Government.
Matthew 是 Thoughtworks 的技术负责人。在担任技术专家的 15 年里,Matthew 一直致力于为零售、能源、电信和政府等大大小小的企业构建解决方案和领导团队。

Photo of John Mikel Amiel Regida

JM is a Senior Consultant at Thoughtworks where he currently plays a Tech Lead and Developer role. He has a strong passion for exploring unknowns, tinkering, working with integrations and greenfield projects.
JM 是 Thoughtworks 的高级顾问,目前担任技术主管和开发人员职务。他对探索未知事物、修补、处理集成和绿地项目有着强烈的热情。


In this article we aim to show why taking an incremental approach to legacy mobile application modernization can be preferable to the classical 'rewrite from scratch' methodology. Thoughtworks has the benefit of working with large enterprise clients that are dependent on their in-house mobile applications for their core business. We see many of them asking their applications to do more and evolve faster, while at the same time, we see an increasing rejection of reputationally damaging high risk releases.
在本文中,我们旨在说明为什么采用增量方法实现传统移动应用程序现代化比经典的“从头开始重写”方法更可取。Thoughtworks 的优势在于与依赖其内部移动应用程序实现其核心业务的大型企业客户合作。我们看到他们中的许多人要求他们的应用程序做得更多、发展得更快,与此同时,我们看到越来越多的人拒绝损害声誉的高风险发布。

As a solution, this article proposes alternative methods of legacy modernization that are based in Domain Driven Design and hinge on the application of the Strangler Fig pattern. While these concepts are far from new, we believe that their usage in mobile applications are novel. We feel that despite incurring a larger temporary overhead from their usage, this is an acceptable tradeoff. We assert how the methodology is used to combat the aforementioned attitudinal shifts in legacy mobile application development while gaining a platform to lower risk and drive incremental value delivery.
作为解决方案,本文提出了基于域驱动设计并取决于 Strangler Fig 模式应用的遗留现代化替代方法。虽然这些概念远非新概念,但我们相信它们在移动应用程序中的使用是新颖的。我们认为,尽管使用它们会产生更大的临时开销,但这是一个可以接受的权衡。我们断言如何使用该方法来对抗上述传统移动应用程序开发中的态度转变,同时获得一个降低风险并推动增量价值交付的平台。

We discuss how this works in theory, diving into both the architecture and code. We also recount how this worked in practice when it was trialled on a large, legacy mobile application at one of Thoughtworks’ enterprise clients. We highlight how the pattern enabled our client to rapidly build, test and productionize a modernized subset of domain functionalities inside an existing legacy application.
我们将讨论理论上的工作原理,深入研究架构和代码。我们还讲述了在 Thoughtworks 的一个企业客户的一个大型传统移动应用程序上试用时,它在实践中是如何工作的。我们重点介绍了该模式如何使我们的客户能够在现有的遗留应用程序中快速构建、测试和生产化现代化的域功能子集。

We move on to evaluate the effectiveness of the trial by highlighting the business facing benefits such as a signficantly faster time to value and a 50% reduced median cycle time. We also touch on other expected benefits that should be used to measure the success of this methodology.
我们继续评估试验的有效性,强调业务面临的好处,例如显著加快价值实现时间和缩短 50% 的中位周期时间。我们还谈到了应该用来衡量这种方法成功与否的其他预期收益。

The Problem with Mobile Legacy Modernization
移动遗留现代化的问题

As applications age and grow, they tend to deteriorate both in quality and performance. Features take longer to get to market while outages and rolled back releases become more severe and frequent. There is a nuanced complexity to be understood about the reasons why this occurs both at the code and organizational level. To summarize though, at some point, an organization will grow tired of the poor outcomes from their software and start the process of legacy replacement. The decision to replace may be made based on multiple factors, including (but not limited to) cost/benefit analysis, risk analysis, or opportunity cost. Eventually a legacy modernization strategy will be chosen. This will be dependent on the organization’s attitude to risk. For example, a complex, high availability system may demand a more incremental or interstitial approach to legacy replacement/displacement than a simpler, less business critical one.
随着应用程序的老化和增长,它们的质量和性能往往会下降。功能需要更长的时间才能进入市场,而中断和回滚版本变得更加严重和频繁。关于在代码和组织级别发生这种情况的原因,需要了解一种微妙的复杂性。总而言之,在某些时候,组织会厌倦其软件的不良结果,并开始替换遗留软件的过程。更换的决定可能基于多种因素做出,包括(但不限于)成本/收益分析、风险分析或机会成本。最终将选择遗留式现代化策略。这将取决于组织对风险的态度。例如,一个复杂的高可用性系统可能需要一种更渐进式或间隙化的方法来处理遗留的替换/替换,而不是更简单、业务关键性较低的方法。

In the case of mobile application modernization, those decisions have in recent memory been reasonably clear cut. A mobile application was often designed to do an individual thing- Apple’s “There’s an app for that” still rings out loud and clear in people’s minds 15 years after the initial batch of advertisements. That message was one that was taken to heart by organizations and startups alike: If you need to do something, write an app to do it. If you need to do something else, write another app to do that. This example struck me when I was pruning the apps on my phone a couple of years ago. At the time I noticed I had several apps from the manufacturer of my car; an older one and a newer one. I also had two apps from my bank; one showed my checking account, another that analyzed and illustrated my spending habits. I had three apps from Samsung for various IoT devices, and at least two from Philips that controlled my toothbrush and light bulbs. The point I’m laboring here is that a mobile application was never allowed to get so complicated, that it couldn’t be torn down, split out or started from scratch again.
在移动应用程序现代化的情况下,这些决策在最近的记忆中已经相当明确。移动应用程序通常旨在执行单个操作 - Apple 的“There's an app for that”在第一批广告发布 15 年后仍然在人们的脑海中响亮而清晰。“这个信息被组织和初创公司牢记在心: 如果你需要做某事,就写一个应用程序来做这件事。如果您需要执行其他操作,请编写另一个应用程序来执行此操作。 几年前,当我修剪手机上的应用程序时,这个例子让我印象深刻。当时我注意到我有几个来自我汽车制造商的应用程序;一个旧的和一个新的。我的银行也有两个应用程序;一个展示了我的支票账户,另一个分析和说明了我的消费习惯。我有 3 款来自 Samsung 的应用程序,适用于各种 IoT 设备,至少还有 2 款来自 Philips 的应用程序,用于控制我的牙刷和灯泡。我在这里要强调的是,移动应用程序从来没有被允许变得如此复杂,以至于它不能被拆除、拆分或重新开始。

But what happens when this isn’t the case? Surely not all apps are created equal? Many believe that the mobile experience of the future will be centered around so-called “super-apps”; apps where you can pay, socialize, shop, call, message, and game, all under one application. To some degree this has already happened in China with “do-everything” applications like ‘WeChat’ and ‘AliPay’- we see the mobile device and its operating system as more of a vehicle to allow the running of these gigantic pieces of software. Comments from industry indicate a realization that the West is not quite as far along as China in this regard. But while not at the super-app, there is no doubt that complexity of the mobile app experience as a whole has increased significantly in recent years. Take the example of YouTube, when first installed, back in the early 2010’s, the application could play videos and not much else. Opening the application today one is presented with “Videos” and “Shorts”, a news feed, controllable categories, subscriptions, not to mention a content editing and publishing studio. Similarly with the Uber app, the user is asked if they want to order food. Google Maps can show a 3D view of a street and Amazon now recommends scrollable product-recommendation mood boards. These extra features have certainly enriched a user’s experience but they also make the traditional build, use, rebuild technique much more difficult.
但是,如果情况并非如此,会发生什么呢?肯定不是所有应用程序都是一样的吗?许多人认为,未来的移动体验将以所谓的 “超级应用程序”为中心;您可以在一个应用程序下进行支付、社交、购物、通话、消息和游戏的应用程序。在某种程度上,这种情况已经在中国发生,例如“微信”和“支付宝”——我们将移动设备及其操作系统更多地视为允许运行这些巨大软件的工具。来自工业界的评论表明,人们意识到 西方在这方面的进步不如中国。但是,尽管不是超级应用,但毫无疑问,近年来,整个移动应用体验的复杂性显著增加。以 YouTube 为例,在 2010 年代初首次安装时,该应用程序可以播放视频,而不能播放其他任何内容。今天打开应用程序,可以看到“视频”和“短裤”、新闻提要、可控类别、订阅,更不用说内容编辑和发布工作室了。与 Uber 应用程序类似,系统会询问用户是否要订购食物。谷歌地图可以显示街道的 3D 视图,亚马逊现在推荐可滚动的产品推荐情绪板。这些额外的功能无疑丰富了用户体验,但它们也使传统的构建、使用、重建技术变得更加困难。

This difficulty can be explained by considering some of the existing common problems of mobile application development:
这种困难可以通过考虑移动应用程序开发的一些现有常见问题来解释:

  • Massive View Controllers/Activities/Fragments
    海量视图控制器/活动/片段
  • Direct manipulation of UI elements
    直接操作 UI 元素
  • Platform specific code  特定于平台的代码
  • Poor Separation of Concerns
    关注点分离不佳
  • Limited Testability 可测试性有限

With discipline, these problems can be managed early on. However, with a large application that has grown chaotically inline with the business it supports, incremental change will be difficult regardless. The solution then, as before, is to build new and release all at once. But what if you only want to add a new feature, or modernize an existing domain? What if you want to test your new feature with a small group of users ahead of time while serving everyone else the old experience? What if you’re happy with your app store reviews and don’t want to risk impacting them?
通过纪律,这些问题可以及早得到管理。但是,由于大型应用程序已经与它所支持的业务无序地发展,因此无论如何,增量更改都将是困难的。然后,像以前一样,解决方案是同时构建 new 和 release。但是,如果您只想添加新功能或对现有域进行现代化改造,该怎么办?如果您想提前与一小群用户一起测试新功能,同时为其他人提供旧体验,该怎么办?如果您对应用商店的评论感到满意并且不想冒险影响它们怎么办?

Taking an incremental approach to app replacement then is the key to avoiding the pitfalls associated with ‘big bang releases’. The Strangler Fig pattern is often used to rebuild a legacy application in place: a new system is gradually created around the edges of an old one through frequent releases. This pattern is well known, but not widely used in a mobile context. We believe the reason for this is that there are several prerequisites that need to be in place before diving headfirst into the pattern.
因此,采用增量方法来替换应用程序是避免与“大爆炸版本”相关的陷阱的关键。 Strangler Fig 模式 通常用于就地重建遗留应用程序:通过频繁的发布,在旧系统的边缘逐渐创建一个新系统。此模式是众所周知的,但在移动环境中并未广泛使用。我们认为这样做的原因是,在一头扎进 pattern 之前,需要满足几个先决条件。

In their article on Patterns of Legacy Displacement, the authors describe four broad categories (prerequisites) used to help break a legacy problem into smaller, deliverable parts:
在他们关于 遗留位移模式的文章中,作者描述了四大类(先决条件),用于帮助将遗留问题分解为更小的、可交付的部分:

  1. Understand the outcomes you want to achieve
    了解您想要实现的结果
  2. Decide how to break the problem up into smaller parts
    决定如何将问题分解为更小的部分
  3. Successfully deliver the parts
    成功交付零件
  4. Change the organization to allow this to happen on an ongoing basis
    更改组织以允许这种情况持续发生

Only in the third point, can we envisage the invocation of the Strangler Fig pattern. Doing so without an understanding of why, what or how it might continue in the future is a recipe for failure.
只有在第三点中,我们才能设想 Strangler Fig 模式的调用。在不了解未来为什么、什么或如何继续的情况下这样做是失败的根源。

Going forward, the article charts how Thoughtworks was able to help one of its enterprise clients expand its existing mobile legacy modernization efforts into a successful experiment that demonstrated the value behind the use of the Strangler Fig pattern in a mobile context.
展望未来,本文描绘了 Thoughtworks 如何帮助其企业客户将其现有的移动遗留现代化工作扩展到一个成功的实验中,展示了在移动环境中使用 Strangler Fig 模式背后的价值。

Satisfying the Prerequisites
满足先决条件

At this point, it seems appropriate to introduce the client that inspired the writing of this article – a globally distributed business with an established retail organization that had embraced mobile applications for many years. Our client had realized the benefits an app brought to provide a self-service experience for their products. They had quickly expanded and developed their app domains to allow millions of customers to take full advantage of all the products they sold.
在这一点上,介绍激发本文写作灵感的客户似乎是合适的 - 一家全球分布式企业,拥有一家成熟的零售组织,多年来一直采用移动应用程序。我们的客户已经意识到应用程序为他们的产品提供自助服务体验带来的好处。他们迅速扩展和开发了自己的应用域,让数百万客户能够充分利用他们销售的所有产品。

The organization had already spent a significant amount of time and effort modernizing its mobile applications in its smaller sub-brands. Responding to a lack of reuse/significant duplication of efforts, high cognitive load in app teams and slow feature delivery, the organization chose a mobile technology stack that leveraged a Modular Micro-app architecture. This strategy had been largely successful for them, enabling proliferation of features common to the organization (e.g. ‘login/registration/auth’ or ‘grocery shopping’) across different brands and territories, in a fraction of the time it would have taken to write them all individually.
该组织已经花费了大量时间和精力对其较小子品牌的移动应用程序进行现代化改造。为了应对缺乏重用/大量重复工作、应用团队 的高认知负荷 和功能交付缓慢等问题,该组织选择了利用模块化微应用架构的移动技术堆栈。这一策略对他们来说在很大程度上是成功的,使组织通用的功能(例如“登录/注册/身份验证”或“杂货店购物”)能够在不同品牌和地区扩散,而所需时间只是单独编写它们所花费的时间的一小部分。

The diagram above is a simplified representation of the modular architecture the organization had successfully implemented. React Native was used due to its ability to entirely encapsulate a domain’s bounded context within an importable component. Each component was underpinned by its own backend for frontend (BFF) that came with the infrastructure as code to instantiate and run it. The host apps, shown above as UK and US, were simply containers that provided the app specific configuration and theming to the individual micro-apps. This ‘full slice’ of functionality has the advantages of both allowing re-use and reducing complexity by abstracting application domains to micro-apps managed by individual teams. We speak in depth about the results of this architecture in the already referenced article on ‘Linking Modular Architecture’.
上图是组织成功实施的模块化架构的简化表示。使用 React Native 是因为它能够将域的边界上下文完全封装在可导入组件中。每个组件都由其自己的 前端后端 (BFF) 为基础,该后端随基础设施即代码一起提供,用于实例化和运行它。主机应用程序(如上所示为 UK 和 US)只是为各个微应用程序提供应用程序特定配置和主题的容器。这种“完整切片”功能的优势在于,既允许重用,又通过将应用程序域抽象到由各个团队管理的微应用程序来降低复杂性。我们在已经引用的 '链接模块化架构' 的文章中深入讨论了这种架构的结果。

As touched upon earlier, the organization’s mobile estate was made up of a number of smaller sub-brands that served similar products in other territories. With the modular architecture pattern tried and tested, the organization wanted to focus efforts on its 'home-territory' mobile application (serving its main brand). Their main mobile app was much larger in terms of feature richness, revenue and user volumes to that of the sub brands. The app had been gaining features and users over many years of product development. This steady but significant growth had brought success in terms of how well-regarded their software was on both Google and Apple stores. However, it also started to show the characteristic signs of deterioration. Change frequency in the application had moved from days to months, resulting in a large product backlog and frustrated stakeholders who wanted an application that could evolve as fast as their products did. Their long release cycle was related to risk aversion: Any outage in the application was a serious loss of revenue to the organization and also caused their customers distress due to the essential nature of the products they sold. Changes were always tested exhaustively before being put live.
如前所述,该组织的移动资产由许多较小的子品牌组成,这些子品牌在其他地区提供类似产品。在对模块化架构模式进行了试验和测试后,该组织希望将精力集中在其“主区域”移动应用程序(为其主要品牌提供服务)上。他们的主要移动应用在功能丰富度、收入和用户量方面比子品牌大得多。在多年的产品开发过程中,该应用程序一直在获得功能和用户。这种稳定但显着的增长在他们的软件在 Google 和 Apple 商店中的评价方面取得了成功。然而,它也开始显示出恶化的特征性迹象。应用程序中的更改频率已从几天变为数月,导致大量产品积压,并使利益相关者感到沮丧,他们希望应用程序能够像其产品一样快速发展。他们的长发布周期与风险规避有关:应用程序中的任何中断都会给组织带来严重的收入损失,并且由于他们销售的产品的基本性质,还会给客户带来痛苦。在上线之前,更改总是经过详尽的测试。

The organization first considered a rewrite of the entire application and were shocked by the cost and duration of such a project. The potential negative reception of a ‘big bang’ new release to their app store customers also caused concerns in the levels of risk they could accept. Suggestions of alpha and beta user groups were considered unacceptable given the huge volumes of users the organization was serving. In this instance, a modernization effort similar to that seen in their sub-brands was believed to be of considerably higher cost and risk.
该组织最初考虑重写整个应用程序,并对此类项目的成本和持续时间感到震惊。应用商店客户对“大爆炸”新版本的潜在负面接受也引起了人们对他们可以接受的风险水平的担忧。鉴于该组织所服务的用户量巨大,Alpha 和 Beta 用户组的建议被认为是不可接受的。在这种情况下,类似于其子品牌中的现代化工作被认为具有相当高的成本和风险。

Thoughtworks suggested an initial proof of concept that built on the successes of the reusability already seen with a modular architecture. We addressed the organization’s big bang risk aversion by suggesting the Strangler Fig pattern to incrementally replace individual domains. By leveraging both techniques together we were able to give the organization the ability to reuse production-ready domains from their modernized mobile apps inside their legacy app experience. The idea was to deliver value into the hands of customers much sooner with less duplication than in a full rewrite. Our focus was not on delivering the most beautiful or cohesive full app experience (-not quite yet anyway). It was about obtaining confidence both in the stability of the iterative replacement pattern and also in how well the new product was being received. These pieces of information allowed the organization to make more informed product decisions early on in the modernization process. This ensured the finished product had been extensively used and molded by the actual end users.
Thoughtworks 提出了一个初步的概念验证,它建立在模块化架构已经看到的可重用性的成功之上。我们通过建议 Strangler Fig 模式 逐步替换单个域来解决组织的大爆炸式风险厌恶问题。通过结合使用这两种技术,我们能够让组织在其旧版应用程序体验中重用其现代化移动应用程序中的生产就绪域。我们的想法是比完全重写更快地将价值交付到客户手中,减少重复。我们的重点不是提供最漂亮或最有凝聚力的完整应用程序体验(至少现在还不完全是)。这是为了获得对迭代替换模式的稳定性以及新产品的接受程度的信心。这些信息使组织能够在现代化过程的早期做出更明智的产品决策。这确保了成品被实际的最终用户广泛使用和成型。

Strangler Fig and Micro-apps
Strangler Fig 和 Micro-apps

So how far did we get with the proof of concept and more importantly how did we actually do this? Taking the learnings from Modular Micro-app architecture (described above), we theorized the design to be as follows:
那么,我们在概念验证方面走了多远,更重要的是,我们实际上是如何做到的呢?从模块化微应用架构(如上所述)中汲取经验,我们将设计理论化如下:

The initial state of the application involved the identification of domains and their navigation routes (Decide how to break the problem into smaller parts). We focused our efforts on finding navigation entry points to domains, we called them our ‘points of interception’. Those familiar with mobile application development will know that navigation is generally a well encapsulated concern, meaning that we could be confident that we could always direct our users to the experience of our choosing.
应用程序的初始状态涉及域及其导航路线的识别 (决定如何将问题分解为更小的部分)。我们专注于寻找域的导航入口点,我们称它们为我们的“拦截点”。熟悉移动应用程序开发的人会知道,导航通常是一个封装良好的问题,这意味着我们可以确信我们始终可以将用户引导到我们选择的体验。

Once we identified our ‘points of interception’, we selected a domain for incremental replacement/retirement. In the example above we focus on the Grocery domain within the existing application. The ‘new‘ Grocery domain, was a micro-app that was already being used within the sub-brand apps. The key to implementation of the Strangler Fig pattern involved embedding an entire React Native application inside the existing legacy application. The team took the opportunity to follow the good modularity practices that the framework encourages and built Grocery as an encapsulated component. This meant that as we added more domains to our Strangler Fig Embedded Application, we could control their enablement on an individual level.
一旦我们确定了我们的“拦截点”,我们就选择了一个用于增量替换/停用的域。在上面的示例中,我们重点介绍现有应用程序中的 Grocery 域。“新”杂货领域是一个微应用,已经在子品牌应用内使用。实现 Strangler Fig 模式的关键是将整个 React Native 应用程序嵌入到现有的遗留应用程序中。该团队借此机会遵循了框架鼓励的良好模块化实践,并将 Grocery 构建为封装组件。这意味着,随着我们向 Strangler Fig Embedded 应用程序添加更多域,我们可以在个人层面上控制它们的启用。

As per the diagram, in the legacy app, Grocery functionality was underpinned by a monolithic backend. When we imported the New Grocery Micro-app, it was configured to use that same monolithic backend. As mentioned previously, each micro-app came with its own Backend for Frontend (BFF). In this instance, the BFF was used as an anti-corruption layer; creating an isolating layer to maintain the same domain model as the frontend. The BFF talked to the existing monolith through the same interfaces the legacy mobile application did. Translation between both monolith and micro-app happened in both directions as necessary. This allowed the new module’s frontend not to be constrained by the legacy API as it developed.
根据该图,在旧版应用程序中,Grocery 功能由整体式后端支撑。当我们导入 New Grocery Micro-app 时,它被配置为使用相同的整体式后端。如前所述,每个微应用都有自己的前端后端 (BFF)。在本例中,BFF 被用作防腐层;创建一个隔离层来维护与前端相同的域模型。BFF 通过与旧版移动应用程序相同的界面与现有单体式应用进行对话。Monolith 和 Micro-App 之间的转换根据需要双向进行。这使得新模块的前端在开发过程中不受遗留 API 的限制。

We continued the inside out replacement of the old application by repeating the process again on the next prioritized domain. Although out of scope for this proof of concept, the intention was that the process shown be repeated until the native application is eventually just a shell containing the new React Native application. This then would allow the removal of the old native application entirely, leaving the new one in its place. The new application is already tested with the existing customer base, the business has confidence in its resilience under load, developers find it easier to develop features and most importantly, unacceptable risks associated with a typical big bang release were negated.
我们通过在下一个优先域上再次重复该过程,继续对旧应用程序进行由内而外的替换。虽然超出了这个概念验证的范围,但其目的是重复显示的过程,直到本机应用程序最终只是一个包含新 React Native 应用程序的 shell。然后,这将允许完全删除旧的本机应用程序,将新的本机应用程序保留在其位置。新应用程序已经在现有客户群中进行了测试,企业对其在负载下的弹性充满信心,开发人员发现开发功能更容易,最重要的是,与典型的大爆炸版本相关的不可接受的风险被消除了。

Diving Deeper… 深入研究...

So far we’ve presented a very broad set of diagrams to illustrate our Mobile Strangler Fig concept. However, there are still many outstanding implementation-focused questions in order to take theory into practice.
到目前为止,我们已经提供了一组非常广泛的图表来说明我们的 Mobile Strangler Fig 概念。然而,为了将理论付诸实践,仍有许多悬而未决的以实施为重点的问题。

Implanting the Strangler Fig
植入 Strangler Fig

A good start might be, how did we abstract the complexity of building both native and non-native codebases?
一个好的开始可能是,我们如何抽象出构建原生和非原生代码库的复杂性?

Starting with the repository structure, we turned our original native application structure inside out. By inverting the control of the native application to a React Native (RN) application we avoided significant duplication associated with nesting our RN directory twice inside each mobile operating system’s folder. In fact, the react-native init default template gave a structure to embed our iOS and Android subfolders.
从存储库结构开始,我们将原始的本机应用程序结构彻底改造。通过将本机应用程序的控制权反转为 React Native (RN) 应用程序,我们避免了与在每个移动操作系统的文件夹中嵌套 RN 目录两次相关的大量重复。事实上, react-native init 默认模板提供了一个嵌入我们的 iOS 和 Android 子文件夹的结构。

From a developer perspective, the code was largely unchanged. The legacy application’s two operating-system-separated teams were able to target their original directories, only this time it was within a single repository. The diagram below is a generalized representation (that is, applicable to both iOS and Android) of the current pipeline from the Client as we understood:
从开发人员的角度来看,代码基本没有变化。遗留应用程序的两个操作系统分开的团队能够定位其原始目录,只是这次它位于单个存储库中。下图是 Client 中当前管道的通用表示形式(即,适用于 iOS 和 Android),正如我们所理解的:

Bi-Directional Communication using the Native Bridge

We’ve already touched on navigation with our previously mentioned ‘points of interception’. It is worth looking deeper into how we facilitated communication and the transfer of control between native and React Native as it would be easy to oversimplify this area.

The React Native ‘Bridge’ enables communication between both worlds. Its purpose is to serve as the message queue for instructions like rendering views, calling native functions, event handlers, passing values etc. Examples of properties passed across the bridge would be isCartOpen or sessionDuration. While an example of a bridge function call might be js invocations of the device’s native geolocation module.

The diagram above also references the concept of a ‘React Native Micro App’. We introduced this concept earlier in the article when we described our app in terms of journeys. To recap though, a micro-app is a self-contained encapsulation of UI and functionality related to a single domain. A React Native app may be made up of many micro-apps similar to the micro frontend pattern. In addition to those advantages we have already discussed, it also allows us to have a greater degree of control over how our Strangler Fig application grows and is interacted with. For example, in a situation where we have more confidence in one of our new journeys than another we are afforded the option to divert a larger proportion of traffic to one micro-app without impacting another.

Bringing both concepts together, we utilized the bridge to seamlessly move our users back and forth across experiences. The ability to pass information allowed us to preserve any immediate state or action from the UI that needed to persevere across experiences. This was particularly useful in our case as it helped us to decouple domains at appropriate fracture points without worrying whether we would lose any local state when we crossed the bridge.

Handling Sensitive Data

So far we’ve discussed moving between legacy and new codebases as atomic entities. We’ve touched on how local state can be shared across the bridge, but what about more sensitive data? Having recently replaced their login and registration (auth) process in their other customer-facing React Native apps with a modular, configurable, brand agnostic one, the client was keen for us to reuse that experience. We set ourselves the task of integrating this experience as an initial demonstration of the Strangler Fig pattern in action.

We leveraged the techniques already discussed to implant the Strangler Fig: i.e. the new authentication journey on the React Native side. When a customer successfully logged in or registered, we needed to ensure that if they moved away from the new experience (back into the legacy journey), their authentication status was preserved no matter where they were.

For this, we utilized the native module code calling side of the bridge. The diagram above explains how we achieved this by using a React Native library that served as a wrapper to save authentication data to the Android EncryptedSharedPreferences or iOS Keychain after a successful login. Due to the flexible structure of the data inside the keystore, it allowed us to seamlessly share the (re)authentication process irrespective of whether the user was in the native or non-native experience. It also gave us a pattern for the secure sharing of any sensitive data between experiences.
为此,我们利用了桥的原生模块代码调用端。上图解释了我们如何通过使用 React Native 库来实现这一点,该库用作包装器,在成功登录后将身份验证数据保存到 Android EncryptedSharedPreferences 或 iOS 钥匙串中。由于密钥库内数据的灵活结构,它允许我们无缝共享(重新)身份验证过程,无论用户是原生体验还是非原生体验。它还为我们提供了一种在体验之间安全共享任何敏感数据的模式。

Regression Testing at Domain Boundaries
域边界的回归测试

An important part of a cutover strategy is the ability to know from any vantage point (in our case, different teams working within the same app) whether a change made affected the overall functionality of the system. The embedded app pattern described above presents a unique challenge in this regard around scalable testability of a multi-journey experience. Moreover one that is managed by multiple teams with numerous branching paths.
直接转换策略的一个重要部分是能够从任何有利位置(在我们的例子中,在同一个应用程序中工作的不同团队)了解所做的更改是否影响了系统的整体功能。上述嵌入式应用程序模式在这方面围绕多历程体验的可扩展可测试性提出了独特的挑战。此外,它由具有众多分支路径的多个团队管理。

UserNative App(maintained byNative Team)React Native (RN) BridgeRN AuthMicro-app(maintained by RN Team)RN Grocery ShoppingMicro-app(maintained by RN Team) Opens App Native app requests theinitialization ofRN Auth micro-app RN Auth micro-appinitializeUser is presented theRN Auth micro-appUser logs in usingRN Auth micro-app User's credentials is sentto the micro-app for processing Request to initializeRN Grocery Shoppingmicro-app Initialize request RN Grocery Shoppingmicro-app initialized User is presented theRN GroceryShoppingmicro-appMicro-app processescredentials & resultsto successful authentication Initializes RN Grocery shopping micro-appbecause of a feature flag

The interaction diagram above shows an example journey flow within the embedded app. One thing to notice is the amount of branching complexity across a journey that is carrying out just two concurrent experiments. We speak more on accidental complexity later in this section.
上面的交互图显示了嵌入式应用程序中的示例历程流程。需要注意的一点是,在仅执行两个并发实验的历程中,分支复杂性的数量。我们将在本节后面详细讨论意外复杂性。

The test pyramid is a well known heuristic that recommends a relationship between the cost of a test (maintenance and writing) and its quantity in the system. Our client had kept to the test pyramid and we found unit, subcutaneous and journey-centric UI-driving tests when we examined their code. The solution therefore was to continue to follow the pattern: Expanding the number of tests across all layers and also extending the suite of journey tests to incorporate the jumping in and out of our embedded Strangler Fig app. But there was a potential problem, ownership. We realized that it would be unreasonable to tie the success of another team’s build to code they did not write or were in control of. We therefore proposed the following test strategy across teams:
测试金字塔是一种众所周知的启发式方法,它建议测试的成本(维护和写入)与其在系统中的数量之间的关系。我们的客户一直遵循测试金字塔,当我们检查他们的代码时,我们发现了单元、皮下和以旅程为中心的 UI 驾驶测试。因此,解决方案是继续遵循以下模式:扩展所有层的测试数量,并扩展旅程测试套件,以纳入我们嵌入式 Strangler Fig 应用程序的跳入和跳出功能。但有一个潜在的问题,那就是所有权。我们意识到,将另一个团队构建的成功与他们没有编写或控制的代码联系起来是不合理的。因此,我们提出了以下跨团队测试策略:

Test Type 测试类型Native 本地React Native React 原生
Unit 单位XX
Subcutaneous 皮下的XX
Legacy Journey 传承历程X
e2e Micro-app Journey e2e 微应用之旅X
Contract tests for interactions with ‘The Bridge’ (journeys with both legacy and micro-app components)
与“The Bridge”交互的合同测试(同时包含旧版和微应用组件的旅程)
XX

On the last table row, by contract we simply mean:
在表格的最后一行中,我们所说的 Contract 仅指:

If I interact with the bridge interface a particular way, I expect a specific event to fire
如果我以特定方式与 bridge 接互,则预期会触发特定事件

For Native to RN interactions, these contracts act as blueprints for micro-apps and enable unit testing with mocks. Mocks simulate the behavior of the micro-app, ensuring it utilizes the required context correctly.
对于 Native 到 RN 的交互,这些合同充当微应用的蓝图,并支持使用模拟进行单元测试。Mock 模拟微应用的行为,确保它正确利用所需的上下文。

The other way around (RN to Native) was similar. We identified the Native functionality we wished to call through the Bridge. RN then provided us with an object called NativeModules which, when mocked, allowed us to assert against the resulting context.
相反(RN 到 Native)是类似的。我们确定了希望通过 Bridge 调用的 Native 功能。然后,RN 为我们提供了一个名为 NativeModules 的对象,当模拟该对象时,它允许我们针对生成的上下文进行断言。

Defining these boundaries of responsibility meant that we could limit the ‘regression-related’ cognitive load on teams through ‘hand-off’ points without compromising on overall app test coverage.
定义这些责任边界意味着我们可以通过“交接”点来限制团队的“回归相关”认知负荷,而不会影响整体应用测试覆盖率。

This strategy was largely well received by both the native and non-native teams. Where we did run into friction was the complexity behind the implementation of the contract tests across the bridge. The team running the legacy application simply did not have the bandwidth to understand and write a new category of tests. As a compromise, for the duration of the PoC, all contract tests were written by the React Native team. From this we learned that any interstitial state required thought to be paid to the developer experience. In our case, simply layering complexity to achieve our goals was only part of the problem to be solved.
这种策略在很大程度上受到了原生和非原生团队的好评。我们确实遇到摩擦的地方是跨桥实施合同测试的复杂性。运行遗留应用程序的团队根本没有带宽来理解和编写新类别的测试。作为妥协,在 PoC 期间,所有合约测试均由 React Native 团队编写。从中我们了解到,任何间隙状态都需要考虑开发人员体验。在我们的案例中,简单地将复杂性分层以实现我们的目标只是要解决的问题的一部分。

Creating the Experiment  创建实验

Bringing everything together to form an experiment was the last hurdle we had to overcome. We needed a means to be able to demonstrate measurable success from two different experiences and also have an ability to quickly backout and revert a change if things were going wrong.
将所有东西放在一起形成一个实验是我们必须克服的最后一个障碍。我们需要一种方法,能够从两种不同的经历中展示可衡量的成功,并且能够在出现问题时快速退出和撤销更改。

The organization had an existing integration with an experimentation tool, so out of ease, we chose it as our tool for metric capture and experiment measurement. For experiment user selection, we decided device level user selection (IMEI number) would be more representative. This was due to the potential for multiple device usage across a single account skewing the results.
该组织已经与实验工具集成,因此出于轻松,我们选择它作为指标捕获和实验测量的工具。对于实验用户选择,我们认为设备级别的用户选择(IMEI 号码)将更具代表性。这是因为单个账户中的多个设备使用可能会使结果产生偏差。

We also utilized the feature flagging component of the experimentation tool to allow us to ‘turn off’ the experiment (revert to native app only) without the need for a release; greatly reducing the time taken to recover should any outage occur.
我们还利用了实验工具的功能标记组件,允许我们“关闭”实验(仅恢复到原生应用程序),而无需发布;大大减少了发生任何中断时恢复所需的时间。

We're releasing this article in installments. The next and final installment will describe the results of this experiment: how it altered time to value and cycle time.
我们将分期发布这篇文章。下一部分也是最后一部分将描述此实验的结果:它如何改变价值实现时间和周期时间。

To find out when we publish the next installment subscribe to this site's RSS feed, or Martin's feeds on Mastodon, LinkedIn, or X (Twitter).
要了解我们何时发布下一期,请订阅本网站的 RSS 提要,或 Martin 在 MastodonLinkedInX (Twitter) 上的提要。


Significant Revisions 重大修订

30 October 2024: Published section on diving deeper

29 October 2024: Published up to Strangler Fig and Micro-Apps