{"id":536,"date":"2022-01-25T16:07:16","date_gmt":"2022-01-25T16:07:16","guid":{"rendered":"https:\/\/fde.cat\/index.php\/2022\/01\/25\/scaling-cross-team-contributions-to-a-native-mobile-app\/"},"modified":"2022-01-25T16:07:16","modified_gmt":"2022-01-25T16:07:16","slug":"scaling-cross-team-contributions-to-a-native-mobile-app","status":"publish","type":"post","link":"https:\/\/fde.cat\/index.php\/2022\/01\/25\/scaling-cross-team-contributions-to-a-native-mobile-app\/","title":{"rendered":"Scaling cross-team contributions to a native mobile app"},"content":{"rendered":"<h3>By Stephen Goldberg, Alex Sikora, and Jean\u00a0Bovet<\/h3>\n<p>Flagship applications are home to myriad functionalities that serve different parts of your userbase. Often, adding a new feature unintentionally causes reduced velocity, single points of failure, and monoliths that are hard to navigate. Such flagship apps are built from contributions from multiple teams each with varying degrees of familiarity with the codebase.<\/p>\n<p>Onboarding a new team to such a codebase is expensive and usually prohibited for reasons\u00a0like:<\/p>\n<p><strong>Onboarding costs<\/strong>\u200a\u2014\u200aIt simply takes too long to bring someone up to speed with the codebase such that they can contribute. The cost of training and mentoring someone to do so is often as high if not more significant than implementing the feature would have been. This investment does not make sense for someone who will develop one feature and then move back to their\u00a0team.<strong>Maintenance cost<\/strong>\u200a\u2014\u200aWho will maintain this feature once the temporary developer has returned to their original\u00a0team?<strong>Quality<\/strong>\u200a\u2014\u200aWhat risk does the new feature, developed by someone without a strong understanding of the architecture and codebase pose to the main application?<\/p>\n<p>We\u2019ve seen this play out before with many enterprise apps, including the Salesforce Mobile app. But there\u2019s a way to enable contributions from other teams while addressing the above challenges. This blog outlines the strategy we used within Salesforce to make it happen: an approach we call our \u201cPlug-in Architecture.\u201d<\/p>\n<h3>Plug-in architecture<\/h3>\n<h4>What is a plug-in architecture?<\/h4>\n<p>In a traditional plug-in architecture, third-party developers create plug-ins that are installed separately from the host application. These plug-ins extend the capability of the host application by adding new features. Typically, the architecture works like\u00a0this:<\/p>\n<p>The host application defines a set of interfaces that the plug-in must implement.The host application injects a set of interfaces into the plug-in, which it may\u00a0use.The plug-in registers itself when it installs, then the host application loads\u00a0it.Diagram showing the dependencies between plugins and implementations<\/p>\n<p>Keep in mind when using this architecture:<\/p>\n<p>The host application shouldn\u2019t depend on a particular plug-in, since users might not install\u00a0it.The host application isn\u2019t aware of a plug-in\u2019s details, it only implements the necessary interface.<\/p>\n<h3>Plug-in architecture and\u00a0mobile<\/h3>\n<p>Engineers create, install, and distribute software packages differently for mobile and desktop environments. In light of these differences, let\u2019s discuss what characteristics of plug-in architecture apply to a mobile app development environment.<\/p>\n<p>Plug-ins are the perfect example of modular architecture. They don\u2019t have any critical dependencies because the host application can run without them. In many cases, the host application doesn\u2019t know what the plug-in does, only that it supports the prescribed interface. Modularity is inherent to most recommended architectures, so your application is probably already structured like\u00a0this.<\/p>\n<p>Plug-ins implement an interface defined by the host app. This interface is an abstraction of what the app needs from the plug-in, and it\u2019s the only dependency between the app and a plug-in. Correctly defining this interface is the most challenging aspect of plug-in architecture. But because all plug-ins conform to this interface, app developers don\u2019t need to write code for integrating a specific plug-in. As mentioned earlier, the app may not even be aware of all the potential plug-ins that a user might\u00a0install.<\/p>\n<p>The app provides an interface to the plug-in. This interface lets the plug-in interact with the host application. The plug-in developers only need to learn this interface and can ignore any other complexity of the host application. This greatly simplifies the learning curve for developers, since the interface is only an abstraction of functionalities a plug-in might need from the application, like sending network requests, storing data, or presenting a\u00a0UX.<\/p>\n<p>Some restrictions of the plug-in architecture, enforced by necessity in a desktop environment, may seem like they could be ignored or relaxed in a mobile application. But it\u2019s best to develop as if these restrictions were still in\u00a0place.<\/p>\n<p>In desktop app development, the host app must run without a particular plug-in, since there\u2019s no guarantee a user will install it. But, in mobile development, the app developer bundles the plug-in\u2019s framework or Android Archive (AAR) with the main app. Plug-ins have to be included at build time and deployed with the main app bundle; you can\u2019t install plug-ins independently of the main application. Since the host application is aware of all available plug-ins at build time, you might think mobile apps don\u2019t need to support graceful degradation without the plug-in, but that\u2019s not the\u00a0case.<\/p>\n<p>For example, suppose that a developer builds the application to depend directly on a plug-in, since the plug-in is \u201calready there.\u201d This dependency breaks the abstraction and makes the application rely on specific versions of the plug-in, which forces the app team to be involved in any maintenance decisions for it. The dependency also implies integration work beyond adding the plug-in to the application, as some code in the app references the specific plug-in. A direct reference extends the work required for a plug-in beyond the interface boundaries.<\/p>\n<p>This is why it\u2019s best practice to gate features, allowing the developer to toggle them on or off if required. If the application cannot gracefully handle the absence of a plug-in, then there is no real opportunity to disable it via a feature\u00a0toggle.<\/p>\n<h3>Implementing a plug-in like architecture<\/h3>\n<p>So how can we replicate the benefits of a plug-in architecture in a mobile app? Most experienced developers are familiar with the values of modularity, loose coupling, and high cohesion. For this article, we won\u2019t focus on these specifically, since they\u2019re primarily achieved automatically by the following two characteristics:<\/p>\n<p><em>Service Provider Interface,<\/em> which exposes services to the\u00a0plug-in.<em>Feature Provider Interface, <\/em>which extends feature implementations from the plug-in to the host\u00a0app.<\/p>\n<h3>The Service Provider Interface (SPI)<\/h3>\n<p>The Service Provider Interface (SPI) is an abstraction that encapsulates all the services the application makes available to the plug-in. This interface allows a plug-in to treat the app as a platform. It helps avoid duplicating common services such as logging, network requests, instrumentation, and others within the plug-in. The application injects the SPI into the plug-in at initialization.<\/p>\n<p>Although Apple and Google both provide network request libraries, most enterprise-class apps have a higher-level network layer responsible for supplying appropriate cookies, request parameters, or other domain-specific overheads. Rather than recreate this in each plug-in, a host application can expose an interface as part of the SPI. Let\u2019s consider this example, and see how we might implement such an\u00a0SPI.<\/p>\n<p>public protocol NetworkRequests {<br \/>    func send(request: POSTRequest, to server: Server)<br \/>}<\/p>\n<p>The following code snippet is a simplistic example of how a host application might implement this abstraction.<\/p>\n<p>class PluginNetwork: NetworkRequests {<br \/>    func send(request: POSTRequest, to server: Server) {<br \/>        let networkEngine = NetworkService.getEngineForUser(user: user)<br \/>        networkEngine.send(request: request, to: server)<br \/>    }<br \/>}<\/p>\n<h3>The Feature Provider Interface (FPI)<\/h3>\n<p>While the SPI abstracts the application\u2019s services to the plug-in, the Feature Provider Interface (FPI) is its counterpart, abstracting the services exposed by the plug-in. The SPI allows the plug-ins to run on top of the host application and avoid bloating the plug-in with redundant service implementations. The FPI, on the other hand, is how the plug-in extends the host application.<\/p>\n<p>A new feature might consist of a root viewController (iOS) or fragment (Android) from which further interaction occurs. The following code snippet demonstrates what an FPI might look like for obtaining the view controller from the\u00a0plug-in.<\/p>\n<p>protocol PluginFeatureProvider {<br \/>    func pluginCanNavigate(to destination: Destination)<br \/>    func controller(for destination: Destination) -&gt; UIViewController<br \/>}<\/p>\n<p>The plug-in will implement this to respond affirmatively if the destination is one the plug-in handles, and likewise returns the appropriate view controller when requested.<\/p>\n<p>class MyPlugin {<br \/>    func pluginCanNavigate(to destination: Destination) {<br \/>        if destination.type == .MyPluginType {<br \/>            return true<br \/>        }<br \/>        return false<br \/>    }<\/p>\n<p>    func controller(for destination: Destination) -&gt; UIViewController? {<br \/>        if destination.type == .MyPluginType {<br \/>            return myPluginViewControllerFactory.createRootViewController<br \/>                                                 (for: destination)<br \/>        }<br \/>        return nil<br \/>    }    <br \/>}<\/p>\n<h3>Our experience with plug-in architecture<\/h3>\n<p>In late 2018, another team at Salesforce reached out to us with a simple question: \u201cHow can we get our feature included in the Salesforce mobile app?\u201d Typically, such a question has an easy answer. \u201cFile a request, and we\u2019ll get back to you once it\u2019s prioritized.\u201d But as more and more teams had similar requests, it became apparent that limiting feature contributions to the Salesforce app development team was not going to scale. So we started asking a new question: \u201cHow can we get these features included in the Salesforce app despite prioritization conflicts?\u201d This is what led to the plug-in architecture discussed in this\u00a0article.<\/p>\n<p>One obvious solution to the feature request was asking the requesting team to spend some time in our mobile development team to do the integration themselves. But as described above: who would maintain it after integration? Who would train their team on the intricacies of our flagship application code? How could we ensure the changes maintain the high quality that our customers expect from\u00a0us?<\/p>\n<p>By introducing a plug-in-like architecture, we could reshape how external teams contribute native features to our mobile app. Both the Salesforce App and the other team sat down to define the shape of the plug-in architecture and its APIs. A few months later, the initial adopter had successfully integrated their feature into the Salesforce app. But more importantly, we had established a successful model in which teams can push new updates to their feature anytime they want with no involvement from the Salesforce app team while maintaining the same level of\u00a0quality.<\/p>\n<p>In late 2019, a company recently acquired by Salesforce wanted to do the same thing. This team didn\u2019t have any inside knowledge of our flagship application nor the intricacies of the Salesforce Platform. Nevertheless, the plug-in architecture let them integrate their product into the Salesforce app in record time. They achieved this because they could focus on a finite and straightforward set of\u00a0APIs.<\/p>\n<p>While these external integrations were happening, we decided to adopt the same plug-in model for features of the flagship application developed by the Salesforce app team members. The idea was to treat features of the Salesforce app the same as external integrations. The architecture enforced a clear boundary for the new feature code, which let developers focus on the value-added by the new feature instead of the main application code. The first internal feature written using the plug-in architecture was for calendar integration and management. The developer worked on the feature for a few days and came back delighted by the simplifications from the plug-in architecture. For example, a secure persistence SPI trivialized storing encrypted data, because the plug-in SPI provided an interface for doing just that. It was up to the host app layer to worry about the implementation. By developing her feature as a plug-in, she could also isolate it to an independent module, making it more easily tested and maintained.<\/p>\n<p>The team is reaping the benefits, with several features in development using this architecture across iOS and Android platforms. The plug-in architecture transforms the Salesforce app into a native development platform with numerous plug-ins running on top of it. This transformation reduces the time it takes to bring features to our\u00a0users.<\/p>\n<p>Additionally, all the plug-ins developed on this architecture can easily be ported, without changes, to another application if needed, provided the other application implements the required plug-in APIs. This portability brings tremendous flexibility for our teams, who can simultaneously deploy their features in their application and the Salesforce app.<\/p>\n<h3>Revisiting external contributions<\/h3>\n<p>At the outset of this post, we discussed a scenario where other teams had feature requests for a flagship application, even offering resources to develop them. The app team rejected this offer for the following reasons: onboarding costs, maintenance costs, and quality concerns from letting an external entity modify the codebase. Let\u2019s look at how the above architectural proposal addresses each of these in\u00a0turn.<\/p>\n<h3>Onboarding Costs<\/h3>\n<p>The plug-in architecture significantly reduces onboarding costs by shrinking the surface space of required knowledge down to the SPI and FPI. In our case, although we offer office-hours-type support, most of the education on the SPI and FPI is done through simple interface documentation.<\/p>\n<h3>Maintenance Costs<\/h3>\n<p>Since plug-ins are modular and exposed as binary modules (frameworks, AARs), the development team that creates them also owns and maintains them. The host app team doesn\u2019t incur any of the maintenance costs.<\/p>\n<h3>Quality Concerns<\/h3>\n<p>As the external team is not modifying the host app code, they pose little risk to the quality of the host app. The plug-in\u2019s impact on the host app is constrained by what the host app exposes via the SPI. The plug-in itself must meet quality standards, as consumers will not distinguish between a plug-in-provided feature and a feature built by the app team. However, the concern here was not the quality of the plug-in feature but the risk of an external contributor modifying the host app code. Plus, due to the modular nature of plug-ins, if coupled with a feature toggle solution, a plug-in can be entirely disabled if there are quality\u00a0issues.<\/p>\n<h3>Conclusion<\/h3>\n<p>Despite initial concerns that this approach would be overly restrictive, developers were surprised when, rather than creating friction and slowing down development, plug-in architecture accelerated it. Developing features as plug-ins shortened the timeframe for integrating features provided by external teams, and made features easier to develop and integrate for internal developers by developing them as plug-ins. We find once developers create a feature with the plug-in architecture, they have an \u201cAha!\u201d moment and become true believers. If your team is looking for ways to enable scalable cross-team contributions to your mobile app, we recommend following a similar approach.<\/p>\n<p><a href=\"https:\/\/engineering.salesforce.com\/scaling-cross-team-contributions-to-a-native-mobile-app-79c4ec9669e5\">Scaling cross-team contributions to a native mobile app<\/a> was originally published in <a href=\"https:\/\/engineering.salesforce.com\/\">Salesforce Engineering<\/a> on Medium, where people are continuing the conversation by highlighting and responding to this story.<\/p>\n<p><a href=\"https:\/\/engineering.salesforce.com\/scaling-cross-team-contributions-to-a-native-mobile-app-79c4ec9669e5?source=rss----cfe1120185d3---4\" target=\"_blank\" class=\"feedzy-rss-link-icon\" rel=\"noopener\">Read More<\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>By Stephen Goldberg, Alex Sikora, and Jean\u00a0Bovet Flagship applications are home to myriad functionalities that serve different parts of your userbase. Often, adding a new feature unintentionally causes reduced velocity, single points of failure, and monoliths that are hard to navigate. Such flagship apps are built from contributions from multiple teams each with varying degrees&hellip; <a class=\"more-link\" href=\"https:\/\/fde.cat\/index.php\/2022\/01\/25\/scaling-cross-team-contributions-to-a-native-mobile-app\/\">Continue reading <span class=\"screen-reader-text\">Scaling cross-team contributions to a native mobile app<\/span><\/a><\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","footnotes":""},"categories":[7],"tags":[],"class_list":["post-536","post","type-post","status-publish","format-standard","hentry","category-technology","entry"],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":302,"url":"https:\/\/fde.cat\/index.php\/2021\/08\/31\/a-brief-history-of-rust-at-facebook\/","url_meta":{"origin":536,"position":0},"title":"A brief history of Rust at Facebook","date":"August 31, 2021","format":false,"excerpt":"Facebook is embracing Rust, one of the most loved and fastest-growing programming languages available today. In addition to bringing new talent to its Rust team, Facebook has announced that it is officially joining the nonprofit Rust Foundation. Alongside fellow members including Mozilla (the creators of Rust), AWS, Microsoft, and Google,\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":814,"url":"https:\/\/fde.cat\/index.php\/2024\/01\/18\/lazy-is-the-new-fast-how-lazy-imports-and-cinder-accelerate-machine-learning-at-meta\/","url_meta":{"origin":536,"position":1},"title":"Lazy is the new fast: How Lazy Imports and Cinder accelerate machine learning at Meta","date":"January 18, 2024","format":false,"excerpt":"At Meta, the quest for faster model training has yielded an exciting milestone: the adoption of Lazy Imports and the Python Cinder runtime. The outcome? Up to 40 percent time to first batch (TTFB) improvements, along with a 20 percent reduction in Jupyter kernel startup times. This advancement facilitates swifter\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":742,"url":"https:\/\/fde.cat\/index.php\/2023\/08\/07\/fixit-2-metas-next-generation-auto-fixing-linter\/","url_meta":{"origin":536,"position":2},"title":"Fixit 2: Meta\u2019s next-generation auto-fixing linter","date":"August 7, 2023","format":false,"excerpt":"Fixit is dead! Long live Fixit 2 \u2013 the latest version of our open-source auto-fixing linter. Fixit 2 allows developers to efficiently build custom lint rules and perform auto-fixes for their codebases. Fixit 2 is available today on PyPI. Python is one of the most popular languages in use at\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":762,"url":"https:\/\/fde.cat\/index.php\/2023\/09\/12\/slack-behind-the-scenes-overcoming-key-challenges-to-craft-a-seamless-mobile-app\/","url_meta":{"origin":536,"position":3},"title":"Slack Behind the Scenes: Overcoming Key Challenges to Craft a Seamless Mobile App","date":"September 12, 2023","format":false,"excerpt":"By Tracy Stampfli and Scott Nyberg In our \u201cEngineering Energizers\u201d Q&A series, we examine the professional life experiences that have shaped Salesforce Engineering leaders. Meet Tracy Stampfli, a Principal Software Engineer for Slack at Salesforce. Tracy works behind the scenes on Slack\u2019s mobile infrastructure team \u2014 an elite group of\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":674,"url":"https:\/\/fde.cat\/index.php\/2023\/02\/06\/the-evolution-of-facebooks-ios-app-architecture\/","url_meta":{"origin":536,"position":4},"title":"The evolution of Facebook\u2019s iOS app architecture","date":"February 6, 2023","format":false,"excerpt":"Facebook for iOS (FBiOS) is the oldest mobile codebase at Meta. Since the app was rewritten in 2012, it has been worked on by thousands of engineers and shipped to billions of users, and it can support hundreds of engineers iterating on it at a time. After years of iteration,\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":281,"url":"https:\/\/fde.cat\/index.php\/2021\/08\/31\/foss-fund-brings-more-than-just-financial-contributions\/","url_meta":{"origin":536,"position":5},"title":"FOSS Fund Brings More than Just Financial Contributions","date":"August 31, 2021","format":false,"excerpt":"One year ago we started the FOSS Fund at Salesforce. We were inspired by our friends at Indeed to give $10,000 every quarter to a project voted on by our open source contributors. We just finished our fourth and final round for the year and the winner is Prettier! Prettier\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/posts\/536","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/comments?post=536"}],"version-history":[{"count":0,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/posts\/536\/revisions"}],"wp:attachment":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/media?parent=536"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/categories?post=536"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/tags?post=536"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}