<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[State Farm Engineering Blog - Medium]]></title>
        <description><![CDATA[A dive into State Farm’s engineering, culture, and more - Medium]]></description>
        <link>https://engineering.statefarm.com?source=rss----58687c4d0d68---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>State Farm Engineering Blog - Medium</title>
            <link>https://engineering.statefarm.com?source=rss----58687c4d0d68---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 07 Jun 2026 12:50:24 GMT</lastBuildDate>
        <atom:link href="https://engineering.statefarm.com/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Then and Now: The Seismic Shift Happening Today in Test Automation]]></title>
            <link>https://engineering.statefarm.com/then-and-now-the-seismic-shift-happening-today-in-test-automation-dfb0fead6cd9?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/dfb0fead6cd9</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-testing]]></category>
            <category><![CDATA[ai]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Wed, 13 May 2026 17:29:45 GMT</pubDate>
            <atom:updated>2026-05-13T17:29:44.647Z</atom:updated>
            <content:encoded><![CDATA[<p>By <a href="https://www.linkedin.com/in/ford-arnett-056979137">Ford Arnett</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*05oOaPIsGjQssGuCsm458w.png" /></figure><h3>Introduction</h3><p>For as long as I can remember, the automation engineer was the loop, not in the loop. Every step of the test automation process required direct human execution: learning framework syntax from documentation and videos, manually translating requirements into test cases, using inspector tools to find every single element, writing each line of code, and updating documentation separately from the codebase. The human had to do all the work, not just guide it.</p><p>Today, we’re experiencing a fundamental shift. The human is now in the loop: guiding, reviewing, and making decisions while AI handles the repetitive execution. We’re not removed from the process; we’re elevated within it, focusing on judgment and strategy rather than tedious implementation details.</p><p>Not long ago, many aspects of UI test automation seemed held back by their very nature. We were trying to shove a square peg in a round hole: creating rigid, step-based, computer-friendly tests to interface with a constantly shifting frontend designed for human users. Now, issues that have kept us scowling at a dimly lit computer screen trying to click a button that’s <em>right</em> there, finally have answers.</p><p>Let’s examine three key areas where these answers are impactful: finding and healing locators, writing test cases and code, and everyone’s favorite — documentation.</p><h3>AI is a Silver Bullet, Isn’t It?</h3><p>AI is certainly the buzzword of the day in the tech community, and the testing sphere is no different. However, let’s not pretend this is a silver bullet. It’s how we apply AI that makes the difference. Every organization wants to race for AI to be the great fix for all our issues. But when used incorrectly we could be looking at spending more time for lesser quality (yikes!).</p><p>I like to think of AI as a great junior developer that doesn’t need any bathroom breaks. Lots of potential, is capable of great work, but needs tasks framed in a particular way. You don’t want to leave out important details, and you can’t assume they know things your team takes for granted. Often, you’ll want to give a bit of the why behind the ask as well. If they can buy into a vision, when you don’t explicitly spell something out, they might be able to use their knowledge to fill in the gaps.</p><h3>Then and Now: Finding Locators &amp; Self-Healing When They Fail</h3><h4>The “Then” — Brittle Locators Were a Constant Battle</h4><p>Remember the feeling? You open your test results Monday morning to find that 30% of your suite is red; not because of actual bugs, but because the development team changed a button’s accessibility label or moved that heading yet again (not that we ever change our minds in tech). The frustrating part? The element you’re looking for is still right there on the screen, easily visible to human eyes, but your test can’t find it.</p><p>Screen elements are notoriously difficult to locate:</p><ul><li>Every UI change means updating locators across multiple test files</li><li>Difficult screens frequently lead to brittle locators</li><li>Different platforms have their own unique challenges</li></ul><p>The cycle was exhausting. Find the broken locator, inspect the new page structure, update the code, run the test, repeat. In my career of leading various automation engineering teams, I’ve seen locator maintenance often take 20–30% of automation engineers’ time and sometimes more. Not the creative problem-solving we signed up for, just tediously tracking down moved buttons and renamed fields.</p><h4>The “Now” — Intelligent Self-Healing Locators</h4><p>We are now leveraging AI to automatically heal broken locators in real time, with multiple fallback strategies and confidence scoring. Modern frameworks can detect when a locator fails and intelligently search for the element using contextual understanding. We’ve taken an open-source framework called <a href="https://github.com/BottleRocketStudios/rocket-fuel-framework-example">Rocket Fuel </a>and added self-healing capabilities. While running automation tests for our State Farm iOS and Android applications, sometimes a locator fails. This could be due to a code change, a dynamic page, locators found for Android but not yet iOS, or any other reason. We then make a call to a large language model (LLM) API with a prompt containing the failure context. This gets paired with the layout of the screen, either image or text, available element metadata, and matches are found. If a match meets the threshold set by the programmer, they will be tried in order of confidence. Tests can then continue execution with the healed locator, as if it never broke.</p><p>Here are some questions we asked and ideas we implemented in our framework:</p><ul><li><strong>Capture context</strong> — What are we looking for? What’s currently on screen? What info do I have to describe what I need?</li><li><strong>Try AI image matching</strong> — try to match by image or use layout to describe what you need. You can even pass this into the next suggestion</li><li><strong>Try AI text analysis</strong> — source of the page or a good text description can be used to generate locators</li><li><strong>Try traditional approaches</strong> — You don’t always need AI! To save some tokens, try standard variations on broken locators. Maybe all you need is a different by or to try Login instead of Log In.</li><li><strong>Give your strategies a weight</strong> so you can rank confidence (1–100, low/medium/high confidence, etc.)</li><li><strong>Cache for current run, output after</strong> — you can reuse healing during the test run, then save the suggestions after. These can then be automatically updated or set for human review.</li></ul><h4><strong>A new spin on a classic problem</strong></h4><p>Imagine you have a login button that your test uses called “login_button”. After a UI update, it is decided “submit_button” better reflects its multi-purpose nature, and the button gets updated.</p><p>When your test runs, the original locator fails. Instead of failing the test for someone to examine later, self-healing activates. The AI analyzes the current page and finds a button with “Log In” text in the appropriate context, near username and password fields. It returns a new locator with high confidence and your test continues successfully. Later the test needs the same button again. After failing, the new locator is pulled from cache and the test continues. After execution, the new locator is written to a file for human review.</p><p>These tests can be run automatically per new build to find broken locators before you’ve gotten your coffee. In our framework, what used to take 30 minutes of manual work now takes less than a minute — a 97% reduction in maintenance time.</p><p>The beauty is you can control when healing runs. During active development when things change frequently, let it heal automatically. Once tests are hardened and stable, you might want to disable it or gate it behind a review process. You stay in control of how much automation you want.</p><h3>Writing Test Cases &amp; Automation Code</h3><h4>The “Then” — Writing Automation Was a Surprisingly Manual Effort</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cTHB3UhKEh4grnsPvrt7vQ.png" /></figure><p>This workflow was highly pattern-based and repetitive. Screen elements were tedious and error-prone to track, with little creative license; you just needed to find them reliably. The elements themselves were highly volatile, changing with every UI update.</p><p>The efficiency question loomed: “Not bad, but is it efficient?” We all knew the answer.</p><h4>The “Now” — AI Handles Translation, We Provide Knowledge</h4><p>UI Automation has been trying to automate the creation of test cases for as long as I can remember. Flawless record and playback at the top of every other vendor’s feature list. And why not! Just watching your tests run feels like a screen recording already! Surely building software to capture that would be easy. Right? Right… As we know, there’s a lot going on under the hood that makes this much harder than it appears. Coming from a programming background, I never loved the idea of losing the level of control that it provides, but this idea of easily building out your test cases by testing what you would anyway is so compelling. Thankfully, we no longer have to choose. Given the limited number of options for interacting with software (tap, scroll, type, click, etc.) and a solid framework, UI automation is well positioned for quality code generation.</p><p>Let’s dive into some specifics for enabling automation engineers to supercharge their workflow. Custom instructions are essentially teaching materials for AI. These are documents that explain your team’s coding patterns, naming conventions, and architectural decisions. Framework-aware extensions, such as Model Context Protocol (MCP) servers or agent skills, extend AI assistants with specialized capabilities specific to your automation framework, allowing them to generate code that follows your exact patterns rather than generic examples. Using custom instructions and framework-aware tools, AI can understand your framework patterns: how to structure page objects, when to create reusable methods versus inline actions, platform differences (perhaps get locators on iOS after getting them for Android), and naming conventions for test methods and variables, just to name a few. An MCP server or agent skills can also standardize many common tasks in automation. This not only helps generate code but also helps onboarding less experienced automation engineers. There’s a whole blog’s worth of content on when to use which tool, so I won’t get into it here. Instead, play around with what you have available to you and see what yields the best results. In my experience, AI-assisted test generation can cut test writing time by roughly 50%, and there’s plenty of room to grow.</p><p>This shift doesn’t replace us, but lets us shift focus. Instead of spending hours finding elements with inspector tools and writing boilerplate code, we’re building on a generated foundation of the team’s design and adding the nuanced business logic that requires human understanding. The edge cases, the business rule validations, the error handling that goes beyond happy paths are where human insight matters.</p><p>I believe maintenance and debugging time is the biggest hidden cost in our test efforts. Noticing patterns, assimilating information, and stepping through code snippets are a large time sink yet essential in debugging. AI can shoulder much of the load here. When tests fail, self-healing handles locator issues automatically. It can also help analyze failure patterns from reports, try a fix, run the test again, and show me the outcome without me doing anything. We’re not spending time on “why can’t we find this button anymore?” we are able to move our focus closer to how users experience an application. Does this feel easy to use? Did the payment amount calculate correctly? Did the workflow follow the expected path? Can I use what we learned years ago when we built a similar feature? That’s where my expertise adds value.</p><h3>Then and Now: Documenting Tests &amp; Integration with Test Management</h3><h4>The “Then” — Documentation Drift was Inevitable</h4><p>We often have barely enough time to write and maintain our tests, meaning keeping up with the test plan can feel like a pipe dream. Then you add on test automation and the complexity balloons. My test case might say “automated: yes” but does that include negative cases? Why is this code different from the documentation? Was this case never updated after the redesign?</p><p>It comes down to the question that has haunted many a team: “what is automation actually covering?” The question is simple enough but faces many hurdles to achieve. For example:</p><ul><li>Automation execution reports are often time consuming to read and tedious to manually tie back to stories</li><li>Documentation is forever outdated</li><li>Discrepancies between code and test plan</li><li>Duplicated tests because someone didn’t know it was covered, or coverage gaps because they thought it was</li><li>Writing documentation is often the lowest priority and skipped</li></ul><h4>The “Now” — Code Becomes the Source of Truth</h4><p>The problem has always been, I have the automation code I want to write, but how do I keep up with all the steps needed to share what it’s doing back to the team? What if we don’t need many (if any) steps to communicate what our tests are covering? At that point, documentation takes care of itself and there’s nothing to keep up with. We don’t need to worry about updating because it updates as we update our code. You might be thinking “that sounds great but how can we get there?” I’m glad you asked.</p><p>If we flip the relationship to make automation code be the source of truth, and documentation is generated from it, then all we need is to translate our code into easily human readable test cases. Turns out, that’s a process AI is really good at.</p><p><strong>Our Code First Documentation System:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*It7b4oP2qpFiD87lUEkZjA.png" /></figure><p>AI is key in this so that it can take the automated test steps and output them to a file. Automated creation/updates in your test case tracking system makes it so your tests are always up to date.</p><p><strong>The transformation:</strong></p><p><strong>Before:</strong> “Is this test case automated?”</p><ul><li>Check the code repository (if you know where to look)</li><li>Ask team members (if they remember)</li><li>Hope for an accurate answer</li></ul><p><strong>Now:</strong> “Is this test case automated?”</p><ul><li>Look at automation stories in the issue tracker</li><li>No uncertainty of what is covered, code is essentially the documentation</li><li>Clear link: User story → Test steps → Automation code → Automated Test Case</li></ul><p>This isn’t revolutionary, frameworks like Behavior Driven Development (BDD) have explored documentation driven by code for years. However, a system like this traditionally required various tradeoffs. For example teams may add an extra layer like Gherkin, try manually keeping up with documentation, or employ a business analyst to help keep documentation synced.</p><p>However, now that we have a tool that can reliably transform code into English steps, we’ve unlocked new possibilities. We can have code independent from a dedicated English layer and still generate documentation from it. Using code as the source of truth, we are no longer building something that tries to match a list of requirements we’re constantly playing catch up with. Instead, when our team asks if something is automated, we show them stories that are as good as code.</p><h3>Conclusion</h3><p>So, what does all this add up to?</p><p>The seismic shift in test automation isn’t just about AI — it’s about rethinking our entire workflow with AI as an enabler. Through this transformation, we’ve seen a 50% reduction in test case writing time, near-elimination of locator maintenance, and a solution to the seemingly unsolvable problem of keeping documentation visible and up to date. As automation engineers we used to be the loop, now we are in the loop. Guiding, reviewing, directing, as we delegate tedious tasks where they should be: our tools.</p><p>Despite the promising returns so far, we are just getting started. We are still experimenting with automation fixing and retry, live smart recovery beyond locators, sophisticated image matching, more AI-assisted debugging. At the rate things are advancing, it feels like there will be new ideas to try out as soon as I finish typing. We are truly experiencing an unprecedented rate of technology growth. All we can do is our best to grow with it and to be flexible.</p><p>Remember: AI is that great junior developer who doesn’t need bathroom breaks. Give it good instructions, clear patterns, and architectural guidance. Review its work critically. Use it to handle the tedious, repetitive work that none of us enjoyed anyway, freeing you to focus on the interesting challenges: the business logic, edge cases, and creative problem-solving that actually need human insight.</p><p>We have the technology. The question isn’t whether to adopt AI in your testing workflow; it’s which problem you’ll solve first. What are you going to build?</p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dfb0fead6cd9" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/then-and-now-the-seismic-shift-happening-today-in-test-automation-dfb0fead6cd9">Then and Now: The Seismic Shift Happening Today in Test Automation</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Responsive Labeling with polygon-labeler — State Farm Open Source]]></title>
            <link>https://engineering.statefarm.com/responsive-labeling-with-polygon-labeler-state-farm-open-source-9e182466bef1?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/9e182466bef1</guid>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[software-engineering]]></category>
            <category><![CDATA[software-development]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Mon, 06 Apr 2026 14:31:02 GMT</pubDate>
            <atom:updated>2026-04-06T14:31:01.697Z</atom:updated>
            <content:encoded><![CDATA[<p>By <a href="https://www.linkedin.com/in/michael-keller-a47575161/">Michael Keller</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CeqeGzoAt73V3FMxsb8Cnw.png" /></figure><h3>Introduction</h3><p>At State Farm, we always try to be a good neighbor and today we are excited to announce we are giving back to the open source community with a new <a href="https://www.npmjs.com/">NPM</a> package for labeling polygons called <a href="https://www.npmjs.com/package/@statefarmins/polygon-labeler">polygon-labeler</a>. In this post, we’ll explore its features, demonstrate how to use it, and highlight what sets it apart from other polygon labeling solutions.</p><h3>Problem</h3><p>Placing labels on polygons sounds simple: find the center of a polygon and render a label there. In practice, this logic produces incorrect label placements or labels that are invisible to the user. At State Farm, we operate our own <a href="https://www.youtube.com/watch?v=bOFcq_qQBxo">internal mapping PaaS</a> and required a solution that was both accurate and high performing for labeling polygons within our platform. While we explored existing packages like <a href="https://www.npmjs.com/package/polylabel">polylabel</a> and <a href="https://www.npmjs.com/package/@turf/turf">Turf</a>, each presented limitations that inspired me to develop polygon-labeler. This lightweight, specialized package consistently computes optimal label points for both individual and grouped polygon features. In this post, I’ll cover common labeling challenges, explain how polygon-labeler addresses them, and show you how to integrate it into your mapping application.</p><h3>polygon-labeler</h3><p>A package for generating a single ideal location to label polygons. This is useful for mapping libraries like <a href="https://maplibre.org/maplibre-gl-js/docs/">Maplibre GL</a> that will generate multiple labels for polygons by default. The package uses an algorithm to find the visual center of polygons and supports grouping features by properties, handling multi-polygons, and viewport clipping for optimal performance.</p><h3>Centroids</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JDtVCQZ77vBtjh2zCOHUGA.png" /></figure><p>When trying to label a set of polygons the first solution that people may gravitate towards is using the centroid of each polygon. While on paper this may sound great there are many pitfalls to using centroids. Turf.js provides a method called <a href="https://turfjs.org/docs/api/centroid">centroid</a> that allows you to easily compute the centroid by passing in each feature.</p><p>Why centroids are a poor default:</p><ul><li>Centroids are calculated via the mean of all vertices within the polygon. For concave shapes, centroids often lie outside the polygon entirely.</li><li>For MultiPolygons and features with disjoint islands, like Hawaii, a single centroid can land in the water between the islands or inside a tiny island instead of the largest visible landmass.</li><li>Map panning and tiling makes the problem worse while choosing centroids. If you pre-compute centroids on geometries but display a clipped view, the centroid may fall outside the clipped area or even worse not at all.</li></ul><h3>Point on Feature</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/973/1*6Mht_Hj7Dp7raWM4Y_rMzw.png" /></figure><p>Using a point that lies on the polygon boundary,for example <a href="https://turfjs.org/docs/api/pointOnFeature">pointOnFeature</a> from Turf, is a common alternative to centroids because it guarantees the returned location is on the geometry. However, this approach has its own pitfalls when used for labeling.</p><p>Why point on features are a poor default:</p><ul><li>The point may end up on the polygon’s outer edge or on a narrow sliver.</li><li>For complex polygons, pointOnFeature often returns a point on a small polygon rather than the visually dominant area.</li><li>Points on edges can cause label collision detection to behave poorly, this leads to polygons not being labeled</li></ul><p>These conditions lead to labels that overlap unrelated features, sit off the polygon, or are invisible on the current map viewport. For readable maps and consistent UX, we need a label position that is visually central and lies on or inside the polygon that is actually visible to the user.</p><h3>Why should I use this package over polylabel?</h3><p><a href="https://www.npmjs.com/package/polylabel">polylabel</a> is a great package, we even use it inside polygon-labeler, to find the pole of inaccessibility (a visually centered interior point) for a polygon, but it solves one of the many problems with creating dynamic labels. polylabel is effective at finding the polygon’s greatest interior point, but it doesn’t address several key challenges involved in labeling polygons within web mapping applications covered below.</p><h3>Key Challenges</h3><h4>Single Polygon Geometry</h4><p>For MultiPolygons it returns a point on every tiny island or disjoint part instead of the visually dominant polygon.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_tJdVey4WKeSima72SnW6A.png" /></figure><h4>Viewport Awareness</h4><p>polylabel does not clip polygons to the current map view. This may lead to the label being generated outside of the current viewport. In the example below, we can see the label was generated outside of the map viewport and would not be visible to the user.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xb3Pq7oCzW06UrD9Wqf2gg.png" /></figure><h4>Fallback Handling</h4><p>In some cases, polylabel cannot guarantee a point within the polygon. This edge case leads to labels being placed potentially in neighboring polygons, in unintentional locations, or nowhere.</p><pre>{<br>  &quot;type&quot;: &quot;FeatureCollection&quot;,<br>  &quot;features&quot;: [<br>    {<br>      &quot;type&quot;: &quot;Feature&quot;,<br>      &quot;properties&quot;: {},<br>      &quot;geometry&quot;: {<br>        &quot;coordinates&quot;: [<br>          [<br>            [<br>              -111.1815838,<br>              45.7768091<br>            ],<br>            [<br>              -111.1816944,<br>              45.776695<br>            ],<br>            [<br>              -111.1812484,<br>              45.7764847<br>            ],<br>            [<br>              -111.1811378,<br>              45.7765988<br>            ],<br>            [<br>              -111.1815838,<br>              45.7768091<br>            ]<br>          ]<br>        ],<br>        &quot;type&quot;: &quot;Polygon&quot;<br>      }<br>    }<br>  ]<br>}</pre><pre>[NaN, NaN, distance: NaN]</pre><h3>Design goals for polygon-labeler</h3><p>When building polygon-labeler</p><ul><li>Guarantee the point is inside the polygon.</li><li>Always return a point that is visually central to the most relevant polygon geometry.</li><li>Respect grouping and unique identifiers so multi-part features are labeled where users expect them.</li><li>Support clipping to the current map view so labels remain visible and meaningful.</li></ul><h3>How polygon-labeler works</h3><p>The package determines optimal label placement through the following steps:</p><ol><li><strong>Group Features</strong>: Features are grouped by a unique identifier property (e.g., “state_name”). All polygons with the same identifier are collected together.</li><li><strong>Clip to Viewport</strong>: Only retain polygons that are clipped to fall within the current map view bounds. This ensures labels are visible on the map.</li><li><strong>Find Largest Polygon</strong>: For each group, the package calculates the area of all polygons and identifies the largest polygon by area. This ensures the label is placed on the most prominent part of the feature.</li><li><strong>Calculate Pole Of Inaccessibility</strong>: Using polylabel, determine the most distant internal point from the polygon outline for the largest polygon.</li><li><strong>Validate Position</strong>: The package checks if the point falls within the polygon boundaries: <strong>If inside</strong>: The pole of inaccessibility is used as the label point. <strong>If outside</strong>: If the point falls outside the polygon, calculate a <a href="https://turfjs.org/docs/api/pointOnFeature">point on the feature</a> to be used.</li><li><strong>Return GeoJSON</strong>: The result is a <a href="https://datatracker.ietf.org/doc/html/rfc7946#section-3.3">GeoJSON FeatureCollection</a> of <a href="https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.2">Point</a> features, each representing an optimal label location with the specified property attached.</li></ol><h3>Installation</h3><pre>npm install @statefarmins/polygon-labeler</pre><h3>Usage</h3><pre>import { getLabelPoints } from &#39;@statefarmins/polygon-labeler&#39;;<br>import type { FeatureCollection, Polygon } from &#39;geojson&#39;;<br><br>const featureCollection: FeatureCollection&lt;Polygon&gt; = {<br>    type: &quot;FeatureCollection&quot;,<br>    features: [<br>    {<br>        type: &quot;Feature&quot;,<br>        geometry: {<br>            type: &quot;Polygon&quot;,<br>            coordinates: [[[0, 0], [0, 4], [4, 4], [4, 0], [0, 0]]],<br>        },<br>        properties: { name: &quot;Area1&quot;, id: &quot;1&quot; },<br>    },<br>    ],<br>};<br><br>// Define map bounds<br>const southWest = { lat: -10, lng: -10 };<br>const northEast = { lat: 10, lng: 10 };<br><br>// Get label points<br>const labelPoints = getLabelPoints(<br>  featureCollection,<br>  &#39;name&#39;,<br>  &#39;id&#39;,<br>  southWest,<br>  northEast<br>);<br><br>console.log(labelPoints);</pre><h3>Output</h3><pre>{<br>    &quot;type&quot;: &quot;FeatureCollection&quot;,<br>    &quot;features&quot;: [<br>        {<br>            &quot;type&quot;: &quot;Feature&quot;,<br>            &quot;geometry&quot;: { <br>                &quot;type&quot;: &quot;Point&quot;, <br>                &quot;coordinates&quot;: [ 2, 2 ] <br>            },<br>            &quot;properties&quot;: { &quot;name&quot;: &quot;Area1&quot; }<br>        }<br>    ]<br>}</pre><h3>Example Image</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XafYdbY1MIXyCU8yeF9A7Q.png" /></figure><h3>Maplibre GL Usage</h3><pre>import maplibregl from &#39;maplibre-gl&#39;;<br>import { getLabelPoints } from &#39;@statefarmins/polygon-labeler&#39;;<br><br><br>let lastBounds: { sw: { lat: number; lng: number }; ne: { lat: number; lng: number } } | null = null;<br>let lastZoom: number | null = null;<br><br>/**<br> * Determine if labels should be recalculated<br> * <br> * @name shouldRecalculateLabels<br> * @param {map} maplibregl.Map A maplibre map<br> * @param {number} tolerance The amount of allowed shift in map bounds in degrees<br> * @returns {boolean} if labels should be recalculated<br> */<br>function shouldRecalculateLabels(map: maplibregl.Map, tolerance = 0.00001): boolean {<br>    const bounds = map.getBounds();<br>    const zoom = map.getZoom();<br>    const sw = { lat: bounds.getSouth(), lng: bounds.getWest() };<br>    const ne = { lat: bounds.getNorth(), lng: bounds.getEast() };<br><br>    if (!this.lastBounds || !this.lastZoom) return true;<br>    if (Math.abs(this.lastZoom - zoom) &gt; 0.01) return true;<br><br>    return (<br>        Math.abs(this.lastBounds.sw.lat - sw.lat) &gt; tolerance ||<br>        Math.abs(this.lastBounds.sw.lng - sw.lng) &gt; tolerance ||<br>        Math.abs(this.lastBounds.ne.lat - ne.lat) &gt; tolerance ||<br>        Math.abs(this.lastBounds.ne.lng - ne.lng) &gt; tolerance<br>    );<br>}<br><br>/**<br> * Generate unique labels for each feature<br> * <br> * @name generatePolygonLabels<br> * @param {map} maplibregl.Map A maplibre map<br> * @param {str} layerName The name of the source for the layer<br> * @param {str} labelField The field used to label each polygon<br> * @param {str} uniqueIdentifierField The field used to distinguish unique features<br> * @returns {boolean} if labels should be recalculated<br> */<br>function generatePolygonLabels(map: maplibregl.Map, layerName: str, labelField: str, uniqueIdentifierField: str) {<br>    if (!this.shouldRecalculateLabels(map)) {<br>        return;<br>    }<br><br>    const bounds = map.getBounds();<br>    const sw = { lat: bounds.getSouth(), lng: bounds.getWest() };<br>    const ne = { lat: bounds.getNorth(), lng: bounds.getEast() };<br>    this.lastBounds = { sw, ne };<br>    this.lastZoom = map.getZoom();<br><br>    polygonFeatures = map.queryRenderedFeatures({<br>        layers: [layerName]<br>    });<br><br>    let polygonFeatureCollection = {<br>        type: &quot;FeatureCollection&quot;,<br>        features: polygonFeatures<br>    }<br><br>    const polygonLabelPoints = getLabelPoints(<br>        polygonFeatureCollection,<br>        labelField,<br>        uniqueIdentifierField,<br>        sw,<br>        ne<br>    );<br><br>    map.getSource(`${layerName}_geojson`).setData(polygonLabelPoints);<br>}<br><br>/**<br> * Reset the zoom and bounds<br> * <br> *  @name clearCache<br> */<br>function clearCache() {<br>    lastBounds = null;<br>    lastZoom = null;<br>}<br><br>map.on(&#39;idle&#39;, () =&gt; {<br>    generatePolygonLabels(map, &quot;polygons&quot;, &quot;name&quot;, &quot;id&quot;);    <br>});</pre><h3>Common pitfalls and recommendations</h3><p>Even with an optimized labeling solution, there are a few best practices to keep in mind when integrating polygon-labeler into your mapping application</p><h4>Avoid static label computation</h4><p>Computing labels only once for an entire dataset and reusing them across all map views is a common mistake. As users pan and zoom, the visible portion of a polygon changes dramatically. A label point calculated for the full geometry may fall outside the current viewport, leaving users with unlabeled features. Instead, either clip geometries to the viewport before computing labels or recalculate labels dynamically as the view changes. The Maplibre GL example above demonstrates this pattern using the shouldRecalculateLabels function to trigger updates only when the map bounds shift significantly.</p><h4>Handle small polygons gracefully</h4><p>For very small polygons, such as tiny islands or narrow slivers, the visual center may be virtually indistinguishable from the centroid. In these edge cases, the sophisticated pole-of-inaccessibility calculation provides little benefit over simpler methods. When dealing with such features, prioritize label visibility and rely on your mapping library’s built-in collision detection to ensure labels remain readable. Consider adjusting label priority or styling to prevent small polygon labels from cluttering the map at certain zoom levels.</p><h4>Cache and throttle updates</h4><p>Recalculating labels on every frame or minor map movement can degrade performance, especially with large datasets. Implement caching strategies and tolerance thresholds to avoid unnecessary recomputation. The example code demonstrates this with lastBounds and lastZoom tracking, only recalculating when the view has shifted beyond a defined tolerance.</p><h3>npm Link</h3><p><a href="https://www.npmjs.com/package/@statefarmins/polygon-labeler">polygon-labeler</a></p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9e182466bef1" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/responsive-labeling-with-polygon-labeler-state-farm-open-source-9e182466bef1">Responsive Labeling with polygon-labeler — State Farm Open Source</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The State Farm® Android Team’s Journey to 99.9+% Crash-Free Sessions]]></title>
            <link>https://engineering.statefarm.com/the-state-farm-android-teams-journey-to-99-9-crash-free-sessions-05399e874327?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/05399e874327</guid>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[software-engineering]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Mon, 02 Mar 2026 15:31:00 GMT</pubDate>
            <atom:updated>2026-03-02T15:31:00.896Z</atom:updated>
            <content:encoded><![CDATA[<p>By <a href="https://www.linkedin.com/in/exampledotcom?utm_source=share&amp;utm_medium=member_mweb&amp;utm_campaign=share_via&amp;utm_content=profile">Andrew Erickson</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HtoZI5RpKy05C-i8kT5TqA.png" /></figure><h3>Introduction</h3><p>The State Farm Android team fiercely defends a 99.9+% crash-free rate for our flagship State Farm® App. A team culture that focuses on availability and resiliency for our customers and a set of coding best practices that ensure a seamless experience when using the app.</p><p>In this post, we’ll take you through our journey of achieving and maintaining a 99.9+% crash-free rate for the State Farm Mobile App through an insider view of our strategies for monitoring and preventing crashes through every day engineering and testing practices.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/381/1*Ch3cUmn23p2YR0N_Mx-ZNg.png" /></figure><h3>A new start: Measuring success</h3><p>A great starting point to our crash journey was in 2017 when the Android team faced a huge engineering endeavor to rebuild our flagship app “Pocket Agent” (which originally launched on Google Play in 2010) with a new app identity. Our new app, the State Farm App, featured a new design system, architectural patterns and features. Around the time of our efforts to build the app, Google Firebase Crashlytics emerged as an essential tool on our tool belt, getting virtually 100% coverage on most crashes. Now that we had a true measure of our crash-free rating, it was game on.</p><blockquote>When you can measure it, you can improve it.</blockquote><p>As we prepared for the big launch of our app redesign, we discovered several new ways to discover crashes and work towards a more resilient app. When we finally went live at the end of 2017, our app’s key features like paying a bill, filing a claim, viewing policy info were all going strong and highly available. We aimed for 99%, and we hit 99.1% crash-free. While we hit our first goal, what we actually had was a wealth of new data through Crashlytics.</p><p>We didn’t stop at 99.1% though; we learned, we modernized and became experts at building an incredibly reliable mobile app… eventually reaching a remarkable, sustained 99.99% crash free rating! At times, we even peaked at an incredible 100% crash-free, with Crashlytics reporting fewer than one crash per 10,000 users. This is no small feat, considering our app has nearly 5 million installs on active devices. According to the Google Play Console, the State Farm Mobile App ranks significantly higher in stability compared to most apps within the Insurance category.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*--r8EV_gPIY_5_WsWlwT9Q.png" /></figure><p>Today, the State Farm App has more than 400 unique screens, calling more than 200 unique endpoints. Every release, we implement new features, enhance existing ones, and increase stability through bug fixes and modernizing our tech frameworks. The engineering team is composed of 15 native Android engineers, and works side-by-side with a dedicated team of manual and automation testers who help ensure every new app release is stable for our customers.</p><h3>Mobile chaos engineering (destructive testing)</h3><p>To fix things, you need to know how your users <strong>can</strong> break them, <strong>before</strong> they break them. While we can code to happy golden path scenarios at our desks, we needed new tools in our tool belts to simulate the real world, and the myriad conditions our app is used in. We doubled down on testing efforts and made a sport out of finding novel ways to find crashes before our customers did.</p><h3>Recovering from Android process termination</h3><p>We also discovered that combining two Android system Developer Options, 1) “Do not keep activities” (DKA) and 2) limiting the background process limit to zero (0PL), were a reliable way of simulating Android process termination. These are settings users can find in their device’s Android OS system settings after enabling a developer mode. It’s not uncommon for users “in the wild” to have these settings enabled thinking they may enhance device performance, when actually increasing the likelihood of unexpected app behavior for many of their apps.</p><p>Enabling these settings revealed to us a more direct way of simulating Android process termination (when the system ends your app’s process due to current resource constraints). For us, this meant that when a user resumed on the same screen days later, but without the data the screen assumed was there, our app would crash. These conditions are also similar to what happens when a user downgrades an app permission via Settings while our app is in the background. There are also other techniques to simulate and/or cause process termination within Android Studio via Logcat (i.e., Force stop application, Kill process and Crash application).</p><p>When process death occurs, the Android OS will still resume the user on the last screen they saw, even if it was last seen days, weeks, or months ago. So a great rule of thumb is to test each screen and how it recovers when a user backgrounds and restores the app with DKA and 0PL enabled. This often uncovers issues when screens don’t independently load their own screen data (e.g., Screen B assuming Screen A called required APIs, then crashing when Screen B gets restored after process death) as well as testing the consequences of long-running async processing finishing after a view has been destroyed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/381/1*3kO9n520sWBs1a-hY2eb8Q.png" /></figure><h3>Simulating unpredictable user behavior</h3><p>This became an endless well of new problems to solve before our big release. In the real world, our users didn’t have the strong connections we had at our desks, they answer phone calls while using the app, they tap around and navigate rapidly, and they run on thousands of different Android devices, all of which reveal crash risks through detached Fragments, localization quirks and device specific nuances.</p><p>Through DKA + 0PL, we started finding that the crashes and the stack traces we found in the Developer Console and Crashlytics were finally replicable, locally and en-masse by simulating what customers often did: open our app from the background after hours, days or months since the last time they used it. What it revealed were state and session management issues, e.g., assuming any given screen had its required data in-memory, when in fact, the Android OS had started it from a clean slate.</p><p>This was the beginning of our destructive testing efforts. We started asking ourselves:</p><ul><li>What happens if I background the screen or navigate away while a service is running?</li><li>What happens if I rotate?</li><li>What happens if I double-tap or tap three buttons at the same time?</li><li>What happens when users have non-US English locales?</li></ul><p>What we found were new problems to solve and more techniques for earning higher crash-free rates.</p><h3>API chaos testing through stubbing responses (test every scenario)</h3><p>Another essential aspect of destructive testing is to test how our app responds to sporadic API failures, so that if things go wrong retrieving and submitting data, our app is ready to gracefully handle anything thrown our way. A key part of this is an in-house “Stub” framework where we can mock the behavior of all 200+ API calls in a highly detailed way, through specifying HTTP status, response time, payload data, and even simulating what happens on subsequent calls to the same service to test recovery from errors. Our stub test suite has more than 2,400 unique scenarios and that number grows each sprint.</p><pre>&quot;sample1&quot; : {<br>    &quot;description&quot; : &quot;Sample API fails on first load, then recovers&quot;,<br>    &quot;expectation&quot; : &quot;User sees an error message. When the error is tapped, or user revisits the screen, then the API is successful and data is loaded.&quot;,<br>    &quot;map&quot; : [<br>      {&quot;status&quot;:500, &quot;matcher&quot;:&quot;.*/endpoint/sample&quot;, &quot;file&quot;:&quot;error&quot;, &quot;isVariablePayload&quot;:true, &quot;sleep&quot;:5000},<br>      {&quot;status&quot;:200, &quot;matcher&quot;:&quot;.*/endpoint/sample&quot;, &quot;file&quot;:&quot;samplePayload&quot;, &quot;isVariablePayload&quot;:true, &quot;sleep&quot;:500}<br>    ]<br>  }</pre><h3>Monkeying around</h3><p>One last wily tool we discovered was Android’s “monkey testing” ADB commands, which allowed us to send tens of thousands of randomly triggered UI events (taps, swipes, system interactions). While coding at our desks, we’re a sample size of one, but we can never predict how our millions of users will use the app, and how the thousands of Android devices, each with their own performance specs and limitations, will perform in the real world.</p><h3>How we monitor releases and what we look for</h3><p>The Android team releases a new version of the app to the Play Store every three weeks. Every release typically has a combination of either new features or feature enhancements, bug fixes and technical upgrades.</p><p>After rounds of dedicated iterative manual testing from engineers and testers as well as our automation team running extensive regression test suites, how do we know that our release is stable and performing well in the public once it goes live?</p><h3>Phased rollouts and early monitoring</h3><p>We start with a phased rollout for a week on Google Play, which allows us to get the latest version out to the public, but at a controlled pace, so that if an early issue arises, we can halt the release, fix it, and continue forward.</p><p>When a rollout begins, Google Firebase Crashlytics alert emails for new crashes and velocity spikes let us know if something requires urgent action within the first hour. Typically, we look for multiple users impacted once, and especially multiple users impacted multiple times (crash looping). This allows us to estimate potential impact and decide what has potential to be an outbreak crash (e.g., will 10, 100, 1,000 users crash in the full release cycle? Does the crash have potential to self-resolve?). The context of a feature matters too: Is it a feature used a thousand times per month or a thousand times per minute? When we clearly see a potential major issue, we’ll chat, collaborate, talk about impacts and decide if it’s best to halt the release and fix now.</p><h3>Two sources of truth: Crashlytics and the Developer Console</h3><p>It was also surprising to learn that you need both Google Firebase Crashlytics and the Google Play Developer Console to see the fullest picture of crashes. The Developer Console often catches crashes that Crashlytics cannot, such as crashes in native code as well as crashes that occur prior to the initialization of the Crashlytics SDK. So checking both places regularly are key to staying on top of stability. For both platforms, prior to release, we also run a “Crashlytics health check” that helps us verify that crash reporting is working as expected and that our obfuscation mapping has successfully been uploaded (so we can easily decipher crashes in the consoles when they occur).</p><h3>Pre-release readiness review with all Android engineers</h3><p>Even before releases, we monitor crashes in a separate Crashlytics test project and verify any major issues introduced in development during our sprint have been properly addressed in our pre-release “Android Readiness Review”. We make sure any open crashes are fixed or closed if necessary. The Android Readiness Review is also a great chance for the team to spend dedicated time reviewing each others features on a release build, create follow-up issues, and make sure our release is ready for our customers in the coming days. We started this Review in response to a few rough patches with crashes a few years back, but have kept it going and continued evolving as it is always fruitful for discussion and resiliency.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/318/1*2GiiR540AUfFMHDj4Va3bg.png" /></figure><h3>Signals from even more channels</h3><p>In addition to crash-oriented metrics, we heavily monitor analytics through multiple dedicated channels, including Adobe Analytics, Splunk and a customer-feedback platform, in addition to Play Store reviews and customer support channels. So even if our crash data suggests app health, we have multiple signals that help give us a full picture.</p><h3>Fix now or fix later</h3><p>When we see lower volume edge case crashes that don’t necessarily require an immediate fix, we still try to prioritize quick fixes in our next sprint to avoid having crashes pile up. Even if just a single user crashed once, if it’s an easy fix (they usually are), then we fix it while working on other sprint feature work. While we always try to replicate all crashes we attempt to fix, it’s not always possible, so we’ll make sure the issue is addressed. Closing crashes in the Crashlytics console and then getting subsequent alert emails about a resurfacing crash is a great way to know when we need to dig further.</p><h3>Crash out loud in dev, log in prod</h3><p>Our golden rule: Users should not experience a crash. The “should never happens” may indeed happen, and if it does, handle it gracefully. Engineers and testers are equally keyed in on covering our happy paths thoroughly and then bulletproofing through destructive testing. For the “should never happen” edge cases, we want to know as soon as possible in sprint development when they do occur. A technique is to explicitly throw crashes in BuildConfig.DEBUG while logging troubleshooting info to Crashlytics using non-fatal event logs for BuildConfig.RELEASE builds.</p><pre>if (theImpossibleHappened) {<br>    if (BuildConfig.DEBUG) throw IllegalStateException(…)<br>    CrashlyticsNonFatalEventLogger.log(&quot;theImpossibleHappened ${moreContextDetailsAboutWhatHappened}&quot;)<br>}</pre><h3>Kotlin non-null assertions (!!) and nullability annotations in Java</h3><p>As we migrated more Java to Kotlin, we had some hidden problems: un-annotated @Nullable fields, particularly data deserialized from network calls. So while our Kotlin code was equipped with null-safety, it can&#39;t handle nulls safely if it doesn&#39;t know about them. So as we saw crashes happen as our mobile API layer change data optionality, one thing was true: <strong>avoid !! like the plague</strong>. It was key that while we moved hundreds of thousands of lines of code from Java to Kotlin, in between, we had to make sure to mark @Nullable on our Java code.</p><p>One thing we learned is never assume a field will be returned. What this means to us: assume all API field data could potentially be null.</p><pre>class SampleSaferVehicleJavaModel {<br>    @Nullable<br>    final String year;<br>    @Nullable<br>    final String make;<br>    @Nullable<br>    final String model;<br>}</pre><pre>data class SampleSaferVehicleKotlinModel(<br>    val year: String?,<br>    val make: String?,<br>    val model: String?,<br>)</pre><p>Along these lines, while we were all learning Kotlin, we needed to become smarter about smart casts, never assuming data is 100% guaranteed or cautiously handling expected data types.</p><pre>fun processResponse(responseData: Any?) {<br>    val isSuccessful = responseData as Boolean // crashes if responseData is null<br>    val isSuccessful = responseData as Boolean? // crashes if responseData is not a boolean<br>    val isSuccessful = responseData as? Boolean ?: false // handles null and non-Booleans safely, with a default value of false.<br>}</pre><h3>Feature flags and Feature Blocks</h3><p>Our apps fully embrace feature flagging as well as the idea of Feature Blocks. If we see a new problem emerge, we can quickly toggle any related feature flags through Google Firebase Remote Config at a granular level (app version specific). Our Feature Block framework lets us block more than 60 unique features within our app with a high level of customization. At a high-level, Feature Blocks are a way to tell customers, “This feature is still here, but we’re working on fixing an issue”. This allows custom messaging, offering alternatives to customers, such as navigating to the equivalent feature on <a href="https://www.statefarm.com/">statefarm.com</a> or through a call-in channel. So in a crash outbreak scenario, our app could temporarily turn off a feature, while letting non-impacted features to continue working as usual. This framework also gives us an opportunity to ask users to update to the newest version of the app on the Play Store to use features that have been repaired.</p><p>Even when things remain stable for long periods of time, feature flags and feature blocks provide us with flexibility, safety, and confidence that when something goes wrong, we can impact the fewest number of users possible.</p><pre>// Feature flagging sample for quickly toggling features remotely<br>if (FeatureFlags.SAMPLE_LOCAL_FLAG.isEnabled &amp;&amp; FirebaseRemoteConfigFeatureFlag.RELATED_SAMPLE_REMOTE_FLAG.isEnabled()) {<br>    handleAutoClaimSelected()<br>} else {<br>    handleAutoClaimSelectedLegacy()<br>}</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*uwD1mI0mxcC3YMwhBD01pg.png" /></figure><h3>Lifecycle safety</h3><p>Before we moved to more modern patterns like MVVM and Jetpack Compose, our MVP and XML-based architecture, detached Fragments were a leading source of crashes.</p><p>A simple example:</p><ul><li>A user submits a preference update (that took 4 seconds on a poor connection).</li><li>After 1 second, the user decides to navigate back.</li><li>The Activity and Fragment are popped from the back stack.</li><li>After 4 seconds passed, the Presenter called back to the View, and the View incorrectly assumed the Activity was present.</li></ul><p>With detached Fragments, calls to Fragment#getString, Fragment#getContext, Fragment#startActivity as well as showing error AlertDialogs would all cause a crash. Ultimately, this revealed flaws where our Presenters were living longer than they should have while also leaking Views. So we got better at testing and removing View callbacks, while leveraging WeakReferences when it made sense.</p><p>In more modern architecture, Compose helps us better manage state and how our data layer triggers updates to the UI, particularly with StateFlow#collectAsStateWithLifecycle.</p><p>Even lifecycle-safe coding practices can be vulnerable to edge cases, either in the form of crashes or unexpected behavior. So a few useful extensions we built:</p><h4>Extension: fun NavController.navigateSafely(…)</h4><p>NavControllers can be prone to edge case crashes, particularly when users double or triple-tap buttons when device resources are low. Each of our NavController#navigate invocations flow throw an exception catching extension. Our extensive manual and automation testing efforts will catch any happy path issues, while the extension covers us on the edge cases.</p><pre>fun NavController.navigateSafely(…) {<br>    try {<br>        this.navigate(…)<br>    } catch (navigationException: Exception) {<br>        Logger.e(TAG, Log.getStackTraceString(navigationException))<br>    }<br>}</pre><h4>Extension: fun LifecycleOwner.isAtLeastStarted()</h4><p>When navigating from Compose UIs, we perform a check to verify that the UI is in an interactive lifecycle state that is ready to navigate. This also prevents multiple destinations from stacking up in the event a user multi-taps different actions on a screen simultaneously.</p><pre>fun LifecycleOwner.isAtLeastStarted(): Boolean {<br>    return lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)<br>}</pre><h3>Remote crash absorbing through UncaughtExceptionHandler</h3><p>In rare cases, background crashes emerge in the wild that aren’t user-facing, but nonetheless still impact crash reporting metrics. App crashes that occur in the background are imperceivable to users, but may interrupt any ongoing background processing work. These types of crashes can happen with new OS releases or mid-release changes in dynamic libraries. While we never want to fail silently, sometimes we choose to absorb 100% background crashes that would have no user impacts.</p><p>We do this through setting a custom UncaughtExceptionHandler. Through Google Firebase Remote Config, we can configure different crash signatures to match on based on their stack trace. In some cases, we may absorb a targeted crash and log the instance to our analytics suite. In other cases, where there’s potential for user recovery, we’ll intercept the crash to show resolution advice in a quick Toast message, before allowing the Android OS to continue with standard crash handling behavior.</p><pre>[<br>  {<br>    &quot;checkForCrashMessageContaining&quot;: &quot;can&#39;t deliver broadcast&quot;,<br>    &quot;toastMessage&quot;: &quot;&quot;,<br>    &quot;absorbCrash&quot;: true,<br>    &quot;developerNotes&quot;: &quot;Handle Google IssueTracker bug /245258072 for Android 13&quot;<br>  }<br>]</pre><h3>Trust but verify 3rd party dependency safety and external integrations</h3><p>Once our team nearly eliminated crashes from our own business logic, lifecycle, or implementation issues, crashes from third party libraries became a top source of crashes. While we vet the technical quality of dependencies we bring in, the magnitude of users, devices, and device conditions means vendors can’t always guarantee things will be 100% stable. So a general approach on the team is to wrap interactions to third party SDK functions in try/catch blocks and log caught exceptions to Google Firebase Crashlytics via non-fatal logging for observability.</p><p>When things do go wrong, it’s important for our team to establish a tight, rapid feedback loop of reporting crashes to our vendor partners so they have visibility on issues, create tickets and prioritize fixes in their products. The State Farm Android team is actually often one of the first companies to surface crashes to vendors, which has a nice benefit of improving stability in the larger Android ecosystem. Reporting SDK crashes helps keep the provider/consumer relationship strong and creates a two-way value proposition.</p><p>Aside from library dependencies, it’s also important to double-check and safely handle launches to third party apps for browser destinations, maps, contacts, calendars and more, since Android users are free to disable and/or uninstall any given app.</p><h3>Ongoing challenges and conclusion</h3><p>Our app continues to grow in features and users and our technology continues to evolve. The latest evolution of our app was <a href="https://engineering.statefarm.com/unifying-our-mobile-experience-how-state-farm-integrated-telematics-into-its-flagship-app-c69b29cdf03f">the merger of the Drive Safe &amp; Safe app with the State Farm Mobile App</a>, which brought telematics capabilities and accident detection to our users. New challenges include working with sensors, background data processing and additional partner integrations.</p><p>Despite our evolving engineering and testing best practices, it’s always unpredictable what can surface through dependency updates, updates in the Android ecosystem (new OS versions, updates to WebView, Chrome, Maps and more). What remains constant is our team’s ability to adapt and carry a team culture of providing the best possible user experience for our customers, keeping an everyday engineering focus on code safety and stability, and recovering from the unexpected.</p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers">https://www.statefarm.com/careers</a></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=05399e874327" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/the-state-farm-android-teams-journey-to-99-9-crash-free-sessions-05399e874327">The State Farm® Android Team’s Journey to 99.9+% Crash-Free Sessions</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From Tension to Trust: Rethinking How Architects and Engineers Work Together]]></title>
            <link>https://engineering.statefarm.com/from-tension-to-trust-rethinking-how-architects-and-engineers-work-together-3f7107167b8b?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/3f7107167b8b</guid>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Fri, 13 Feb 2026 16:02:56 GMT</pubDate>
            <atom:updated>2026-02-13T16:02:54.831Z</atom:updated>
            <content:encoded><![CDATA[<p>By <a href="https://www.linkedin.com/in/ben-justick-64b83b78">Ben Justick</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dn5QxmzqmVey9a27qWetug.jpeg" /></figure><h3>Introduction</h3><p>Engineers and Architects have both played important roles in designing and building solutions from as far back as I can remember. However, the working dynamics between these roles have changed a lot over the past 10+ years as the industry and technology has advanced at a rapid pace around us. The days of groups of Architects being huddled together in a room for weeks coming up with the “perfect” design to hand over to Engineering teams have come and gone, and have been replaced by teams of Engineers collaborating in real time to iterate on a design. As Engineers have continued to grow their architectural acumen and responsibilities over time, I’ve started to observe some quiet tension start to build between some groups of Architects and Engineers as both groups struggle to adapt and find ways of working together that are mutually beneficial. Some Architects have a tendency to lean harder into the past and produce more design documentation in a bubble primarily to be consumed only by other Architects. Meanwhile, some Engineers are starting to produce the design documentation they need by themselves without needing to engage an Architect.</p><p>There’s a way to address this expanding rift, but it’s going to take changes in mindset and behavior from both sides. I’ve personally observed and experienced how a great partnership between an Architect and an Engineer can be a true game changer when it comes to taking a great idea and making it a reality. In this post, I’ll expand on the key behaviors and practices that I feel are the most essential to maximize this partnership for mutual success.</p><h3>What Makes Us So Different?</h3><p>In order to better understand how we can work together, we need to spend a bit of time digging into what makes us different. I realize that not all Engineers and Architects will fall into these patterns and there may be some overlap in tendencies depending on the person. However, these are the key tendencies that I’ve observed over the years of working with Architects and Engineers in many different areas of focus and contexts.</p><h3>Key Tendencies of Engineers</h3><ul><li>Excel at coming up with new concepts and ideas that make use of new technologies.</li><li>Prefer to convey new ideas by writing code.</li><li>Tend to care more about building out end-to-end solutions rather than navigating organizational dynamics and edge cases.</li><li>Very productive working independently.</li><li>Tend to think more practical and near term.</li></ul><h3>Key Tendencies of Architects</h3><ul><li>Excel at taking complex ideas and making them easy for others to understand.</li><li>Prefer to convey new ideas by creating diagrams and visualizations.</li><li>Tend to think a lot about organizational alignment and agreements that need to be made across area boundaries to build out an end-to-end solution, and non-functionals/edge cases that could “break” a solution.</li><li>Very productive working in groups and leading group collaboration activities.</li><li>Tend to think more abstractly and long-term.</li></ul><p>Are you seeing a pattern emerging here? Engineers and Architects both want to design and build great solutions at the end of the day, but the things they tend to care about the most and their strengths are almost at direct odds with each other. Being aware of these strengths and weaknesses is the first step in learning how to maximize this partnership in a way that leans in to the things that each role does best while also working proactively towards growth.</p><h3>Collaboration for Acceleration</h3><p>A few years ago, I moved to a new area in our organization and was paired up with a very talented Engineer that was working on the design and build of a solution that was targeted at enabling a new bundled quote and purchase experience in the Customer Channel. He had been working for a couple of months on a new API that would be needed to enable this experience and had made significant initial progress on the API interface design and writing some of the code that would enable this design. My initial assignment was to work with him to refine the API interface design, but we soon realized we could do a lot more than that.</p><p>This new API was targeted to be fully built and owned by a product team that had a lot of experience in developing frontend components, but limited experience with developing backend APIs. Handing over an API interface design spec along with some partially written code to this team and saying “get to work” on building this out would have certainly been a slow, confusing mess. However, this Engineer realized that he could lean in to the skillset that I had to help with enabling the target team with getting up to speed with what needed to be built in a way that they could hit the ground running as effectively as possible. This started with some detailed code walkthroughs/reviews to gain an understanding of how the code was structured, what code was already written, and what code was left to build out. This gave me what I needed to then create both high level and detail level architecture documentation that was targeted at initially getting the team to see the full scope of the solution we needed to build, and then easing them into the details of the specific work that was needed. I walked the team through sequence diagrams instead of code, so they could understand the problem from a logical perspective before trying to think about how to adjust to a new context and syntax. By working together, we were able to rapidly bring this team up to speed with the design and the work they needed to do next.</p><p>Obviously, this approach worked really well, or I wouldn’t be telling this story, but what are the key behaviors that led to that success?</p><p>From the perspective of the Engineer, it was:</p><ul><li>Acknowledgement that architectural documentation could help bring the team up to speed with the vision and details faster than with just the API interface spec and code alone.</li><li>Willingness to invest time in walking through the details of the code to bring an Architect up to speed with the details.</li></ul><p>From the perspective of the Architect, it was:</p><ul><li>Being vulnerable to step outside of the typical architecture comfort zone to build an understanding of the details of a code base.</li><li>Willingness to go beyond the initial parameters of the assignment to create detailed solution architecture documentation that could help to accelerate a product team.</li></ul><h3>Getting the Organization Aligned to Enable Big Ideas</h3><p>More recently, I’ve been working as part of our Enterprise Architecture area, and I’ve observed how the same types of collaborative behaviors between Architects and Engineers can be applied at a different level to achieve great outcomes. A few months ago, one of our Principal Engineers informally met with me and a couple of other Architects to talk about the high level vision for a new solution that would enable end-to-end traceability across our systems. Over the course of this initial conversation, it was clear that this Engineer had already spent a good amount of time researching and thinking about this and had already jumped ahead to identifying detailed solution and design ideas. However, there was a need for multiple leaders across the department to become aligned on a common vision and outcomes, or these ideas were just going to remain just that… ideas. We already had several teams that owned solutions that provided a portion of the capabilities that were needed for traceability, and there was some perceived overlaps in scope of ownership/responsibility across these teams. There were also several other areas across the department that were very interested in specific outcomes related to a solution like this, and some of them started to build out their own “homegrown” solutions to address the parts of the scope they cared about the most.</p><p>We needed to get our department aligned on what the problem was, who the key players in the space were, the key gaps, and next steps that needed to start moving forward in the short term to begin to enable the longer term vision. This is where Architecture was the perfect fit. A small group of Architects and I were able to create a set of visuals in a relatively short timeframe that effectively broke down the problem space and provided a recommended near term plan of action for 1st and 2nd line leaders across the department to react to. As we iterated on our work, we shared our draft documentation back with the Principal Engineer that initially reached out to ensure we were on the right track and make updates based on feedback. Our visuals included:</p><ul><li>A scope breakdown to clarify what was core to the problem space vs. non-core/adjacent scope.</li><li>A view of the different products in the current state that were solving for parts of the problem that showcased the points of overlap between these solutions.</li><li>A gap analysis to show where we currently had traceability across different platforms within one or more current state solution vs. where we had gaps.</li></ul><p>We shared our output as a pre-read to the 1st and 2nd line leader group and though we setup an hour to discuss and walk through the documentation, we only needed 30 minutes because the documentation spoke for itself. It helped to clear up the different ideas of what was meant by “traceability” in everyone’s heads and illustrate exactly where the current state overlaps in products were rather than everyone going off of a general feeling that there was redundancy. It also helped to get everyone focused on the gaps that needed to be addressed collectively by the teams that were already working in this space and provided a clear plan of action for bringing these teams together to work on near term next steps.</p><p>Would it have been possible for our Principal Engineer to get the key players in the department aligned and focused without engaging Architects to help with articulating the vision? Maybe, but it likely would have taken a lot longer with significantly more potential for organizational swirl. Instead, an Engineer decided to engage Architecture for help/assistance with one of the things that they do best, and was able to rapidly gain organizational support for a big idea.</p><h3>Giving Architecture a Reality Check</h3><p>All of the examples I’ve provided so far have been stories of Engineers taking the initiative to engage with Architects. So, what does it look like when the engagement starts in the other direction? To be honest, it’s hard for me to highlight one specific story because it happens so frequently and usually has a really short feedback loop. So, I’ll highlight how this usually goes more abstractly to illustrate the general pattern.</p><p>As an Architect, I get asked to think through a lot of different high level problems at a conceptual level, and I’m often asked to create some initial conceptual architecture diagrams to illustrate possible high level design options. These are often the “boxes and lines” diagrams that a lot of Engineers aren’t the biggest fans of, because they often can provide a false impression of the complexity or amount of work necessary to actually enable the solution. It’s these types of situations where early engagement with some trusted Engineers can result in the feedback needed to either make some key adjustments to a proposed conceptual architecture or to even eliminate possible design options before they get shared with other Engineers, Architects, or Leadership audiences.</p><p>There are several Engineers I’ve collaborated with over the years that I’ll reach out to for informal architecture review requests in situations like this. In many cases, I find it ideal to review initial design concepts with an Engineer that may be fairly close to the problem space and a different Engineer that might not be familiar with the scope of the problem space at all. This helps to vet the conceptual design from a practical perspective to ensure that there aren’t major technical feasibility issues or missing points of integration that need to be represented to better illustrate the potential complexity, while also ensuring that the design is easy to understand and consume by a more general engineering audience. It would be impossible for me to count how many times I’ve reached out to an Engineer with “got a few minutes to look at something…”, and within a few minutes of hopping on a call, some critical insights are shared with me that either make or break the design. While it requires me to be vulnerable about draft designs that aren’t fully “baked” yet, it’s so much better to find out a critical flaw in a design early and either adjust or scrap it entirely rather than continuing to take time and effort iterating on something just to find out later with a much wider audience that you should have done your homework.</p><h3>Building Connections Organically</h3><p>Over the course of this post so far, I’ve provided you with some practical examples of the mutual benefit to be gained through close collaboration between Architects and Engineers. However, unless you’re used to already working in that way, it takes some focused and intentional effort to break through those invisible barriers of misunderstanding. This starts with a mindset shift to be more vulnerable on both sides. For Architects this often means letting go of the potential fear of harsh criticism and/or being willing to admit when you don’t have a detailed enough understanding of how the code works and taking the next step to reach out to an Engineer for help. For Engineers this often starts with the acknowledgement that there’s value in good design documentation and having a willingness to engage with an Architect to help with creating the documentation you need to articulate your ideas to the masses. Sometimes a seemingly small behavioral and/or mindset shift is all that’s needed to reignite the spark of collaboration between an Architect and an Engineer. This may mean that you need to lean away from some of your existing tendencies for a bit and lean into someone else’s tendencies to demonstrate that you value their perspective and contributions. This starts to build a foundation of trust that will begin to go both ways over time, but you can’t expect the other person to acquiesce and lean into your tendencies first.</p><p>When you’re ready to take that next step to build or foster a connection, sometimes all it takes is reaching out to that Architect or Engineer you’ve worked with on a recent effort and asking, “Hey, any chance you have a few minutes to take a look at something I’m working on?”. The person on the other side of that request will more than likely feel honored that you reached out to them for their opinion, and likely will end up returning the favor.</p><p>Finding someone to reach out to could prove to be more challenging if you are new to an organization or if your organization is structured in a way where Architects and Engineers don’t often collaborate organically all that often. In these cases you may need to rely your manager, a mentor, or a peer for help with identifying someone that could be a good new connection for you, and you may want to approach this in the form of a cross-mentoring opportunity to begin with. These present a great opportunity for Architects to possibly take a deeper dive into a code base to build their low level design acumen, and/or for Engineers to expand their high level design acumen and diagramming skills in a setting that’s all about growth and learning.</p><h3>Leadership’s Role in Creating and Fostering Perfect Pairings</h3><p>While most of this post has been focused on the behaviors of Architects and Engineers, leaders also play an essential role in ensuring strong collaboration and partnership. Leaders should invest in understanding the key tendencies of the Architects and Engineers within their purview to look for opportunities to pair people together that may already have some overlaps in tendencies. This will create an ideal situation where the Architects and Engineers involved should be able to collaborate naturally and hit the ground running when it comes to sorting out and driving the work that needs to get done. Reflecting on the examples above, this was the case with the first example, where a strong strategic leader was able to pair me up with an Engineer that was a great fit for me, and I was able to start producing results almost immediately when I moved to the new area.</p><p>In an ideal world, leaders would always be able to create perfect pairings, but there are many situations where the people within a leader’s purview may be limited, and the Architects and Engineers may have very strong leans towards their respective tendencies. In these situations, it’s recommended leaders be more engaged and proactive in fostering and building the partnership and collaboration they would like to see between roles. This may involve taking on a more proactive role in leading initial working sessions to assist with breaking down the work and ensuring it gets assigned appropriately. It may also involve being proactive in showcasing and highlighting the benefits of the contributions and output that each role is providing in context to the overall work effort. While some Architects and Engineers may be able to effectively showcase and advocate for the value of their work, in these situations sometimes an extra boost from a leader that really sees the value of both roles is what is needed to break down pre-existing barriers and reshape people’s perspectives.</p><h3>Conclusion</h3><p>Over the course of this post, I’ve provided a breakdown of why the working relationships between Architects and Engineers often can have so much tension and several real-life examples of why it’s vital to change individual behaviors and mindsets to work through that tension to get to a place of trust if you want to maximize the value of both roles within your organization. In order to truly be effective, this involves change at the individual level for Architects and Engineers, and strong proactive leadership that understands both the tendencies and the value that each role can provide in context to the work at hand. While changes in mindset usually occur over time, small behavioral changes can start right away. So, what are you waiting for? Take some small action today to build or foster a connection. A small action today will eventually lead to more significant changes in mindsets over time that will result in trust and collaboration between Architects and Engineers that will lead to amazing outcomes.</p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3f7107167b8b" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/from-tension-to-trust-rethinking-how-architects-and-engineers-work-together-3f7107167b8b">From Tension to Trust: Rethinking How Architects and Engineers Work Together</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unifying Our Mobile Experience — How State Farm Integrated Telematics Into Its Flagship App]]></title>
            <link>https://engineering.statefarm.com/unifying-our-mobile-experience-how-state-farm-integrated-telematics-into-its-flagship-app-c69b29cdf03f?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/c69b29cdf03f</guid>
            <category><![CDATA[mobile]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[android]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[ios]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 13:45:22 GMT</pubDate>
            <atom:updated>2025-09-04T13:45:22.236Z</atom:updated>
            <content:encoded><![CDATA[<h3>Unifying Our Mobile Experience — <strong>How State Farm Integrated Telematics Into Its Flagship App</strong></h3><p>By <a href="https://www.linkedin.com/in/scott-anderson-89b7a21a0">Scott Anderson</a> and <a href="https://www.linkedin.com/in/travis-kessinger">Travis Kessinger</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JrL7XKT9BsZ0x1c4lIf17A.jpeg" /></figure><p>We’ve had the privilege of working on the State Farm Mobile app for quite some time — Scott on Android (all the way back when the app was called Pocket Agent!) and Travis on iOS. Over the years, the app has undergone major transformations, from a complete rewrite to add tablet support, to a full redesign in 2017 — the same year it got its current name. In 2020, we added support for dark mode and rebranded the app to align with State Farm’s modernized vision.</p><p>Starting in September 2023, we faced our next big challenge: merging our standalone telematics app, Drive Safe &amp; Save®, into the State Farm Mobile app. This effort spanned both Android and iOS platforms, requiring innovative solutions to deliver a unified experience across millions of devices.</p><p>In this post, we’ll share why we made this move, how we planned a seamless migration for users, the engineering challenges (and wins!) behind the scenes, and what we learned along the way. Plus, we’ll reveal the real-world impact with hard numbers and reflect on how this integration paves the way for future telematics innovation at State Farm.</p><h3>Why Combine the Apps? Listening to Our Customers</h3><p>Let’s talk about the two apps at the center of this effort. First, there’s Drive Safe &amp; Save, a telematics app designed to empower drivers to personalize their auto insurance premiums based on how they drive. Using driving data, the app offers feedback to help users improve their driving habits. It also includes a feature called Accident Assistance, which detects crashes and can automatically notify emergency services, reducing response times and potentially saving lives.</p><p>Then there’s the State Farm Mobile app, our flagship app. It’s built around three key areas that customers rely on: Insurance, Claims, and Billing and Payments. It’s the go-to app for managing policies, viewing and downloading insurance cards, paying bills, and starting claims all in one place.</p><p>So why combine these apps? Simply put, it’s what our customers wanted. In a 2021 survey, about 80% of respondents said they preferred a single, integrated app experience. At the time, most users were only interacting with one app: the State Farm Mobile app or Drive Safe &amp; Save, but not both. By merging the two, we’re giving more customers access to more features while simplifying their experience. For users, this means fewer apps to manage and more value in one place. For us as engineers, it meant tackling a unique and challenging migration to make this a reality.</p><h3>The Challenge: Migrating Millions Without Missing a Beat</h3><h4>Planning the Migration: Strategy and Rollout</h4><p>To ensure the migration proceeded smoothly, we implemented a controlled rollout strategy based on users’ auto policy state. This allowed us to implement the migration flow with a subset of users before expanding to the broader user base.</p><p>On both the Android and iOS platforms, the rollout was managed using Firebase Remote Config, where we maintained a list of auto policy states eligible for migration. This configuration allowed us to dynamically update rollout criteria without needing app updates or disruptions for users:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/592/1*PMrmvyK_iF-6K-RZjJX12w.png" /></figure><h4>Guiding Users Through a Seamless Transition</h4><p>When a user in the rollout group launches the Drive Safe &amp; Save app, they are greeted with an “It’s moving day!” screen. This screen includes a button to initiate the migration process by launching the State Farm Mobile app. At this point, the Drive Safe &amp; Save app continues to record trips and provide Accident Assistance to ensure there’s no interruption in functionality until the migration is complete.</p><p>Tapping the “Go to the State Farm app” launches a Firebase Dynamic link to direct users to the State Farm Mobile app, where the migration flow begins:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2Xt_VBvUBEajiTj93_oHzA.png" /></figure><h4>Permission Acceptance and App Deactivation</h4><p>After logging into the State Farm Mobile app, users are required to accept all necessary permissions to enable trip recording. After the user accepts the permissions, a configured intent is broadcast to the Drive Safe &amp; Save app, signaling it to deactivate.</p><p>Deactivation involves:</p><ul><li>Turning off trip recording in the Drive Safe &amp; Save app</li><li>Disabling the Accident Assistance feature, which is now handled by the State Farm Mobile app.</li></ul><h4>Balancing Complexity and User Experience</h4><p>One of the biggest challenges in this migration process was balancing technical complexity with user experience. The goal was to make the migration flow as intuitive as possible while maintaining safeguards to ensure data integrity and continuity of features. By leveraging Firebase tools and carefully designed app interactions, we were able to achieve a migration experience that was controlled and user-friendly.</p><h3>Engineering at Scale: How We Made It Happen</h3><h4>Modern Mobile Architecture: Under the Hood</h4><p>To successfully merge Drive Safe &amp; Save into the State Farm Mobile app, we needed a solid foundation that could support new features, scale to millions of users, and allow for rapid development on both Android and iOS. This migration was more than just moving code — it was an opportunity to modernize and align our architectural patterns across both platforms.</p><p>We focused on:</p><ul><li><strong>Modularization:</strong> Continue breaking features into independent modules for better code ownership and parallel development.</li><li><strong>Feature Flagging:</strong> Using local and remote configuration to safely control rollout and minimize risk for users.</li><li><strong>Reactive State Management:</strong> Adopting modern, reactive patterns to keep UI and data in sync.</li><li><strong>Modern UI Frameworks:</strong> Leveraging Jetpack Compose and SwiftUI to accelerate development and improve maintainability, even as we integrated with our established codebases.</li><li><strong>Robust Security and Privacy:</strong> Ensuring all telematics features were migrated with strict attention to user permissions and data protection.</li></ul><p>With this foundation in place, our Android team concentrated on Jetpack Compose and Model-View-ViewModel (MVVM) to deliver scalable, maintainable features, while our iOS team integrated a new tab using SwiftUI within our established UIKit app for a seamless user experience. In the following sections, we’ll share some technical details and insights from each platform.</p><h3>Android: Jetpack Compose, MVVM, and Type-Safe Navigation</h3><p>When we began developing the new Drive Safe &amp; Save feature within the Android version of the State Farm Mobile app, the team already had some experience implementing features with Jetpack Compose and Model-View-ViewModel (MVVM) architecture using StateFlow. This migration effort gave us the opportunity to build on that foundation and gain even more valuable experience with these tools.</p><p>The Drive Safe &amp; Save feature includes over 30 screens. By implementing consistent patterns across these screens, we were able to significantly improve development speed, quality, and maintainability. Below are a few ways we leveraged Compose and MVVM principles during this project:</p><h4>Type-Safe Navigation with the Navigation Component for Compose</h4><p>For the Drive Safe &amp; Save feature, we used the Navigation Component for Compose to manage navigation between composables. This allows us to take advantage of <a href="https://developer.android.com/guide/navigation/design/type-safety">type-safe navigation</a>, reducing the risk of runtime errors and improving code readability:</p><pre>private fun navigateToVehicleDetailsScreen(lifecycleOwner: LifecycleOwner, uniqueVehicleKey: String, navHostController: NavHostController) {<br>    if (!lifecycleOwner.isAtLeastStarted()) {<br>        SFLogger.d(TAG, &quot;onNavigateToVehicleDetailScreen called, but lifecycle not at least started: not navigating&quot;)<br>        return<br>    }<br><br>    val route = DssNavigationDestination.VehicleDetailsTO(uniqueVehicleKey)<br><br>    SFLogger.d(TAG, &quot;Navigating to $route&quot;)<br>    navHostController.navigateSafely(route)<br>}</pre><h4>Reusable Composables for Consistency and Efficiency</h4><p>To ensure consistency across screens, we embraced creating reusable composables. These composables are prefixed with “Sfma” to standardize their naming to indicate reusability. For example, we leveraged an SfmaCard reusable composable for the “About your discount” screen:</p><pre>SfmaCard(<br>    sideMarginResourceId = baseR.dimen.sfma_screen_side_margin_always_zero,<br>    topBottomMarginResourceId = baseR.dimen.sfma_screen_side_margin_always_zero,<br>    backgroundColor = SfmaCardBackgroundColor.GRAY,<br>    strokeColor = SfmaCardStrokeColor.NONE,<br>) {<br>    Text(<br>        modifier = Modifier.padding(24.dp),<br>        text = stringResource(id = R.string.dss_about_your_discount_reminder_body),<br>        style = sfmaTextStyleBody(),<br>    )<br>}</pre><p>SfmaCard is now being used hundreds of times in the app, helping to ensure consistency and maintainability.</p><h4>State Management with StateFlow Emitting Repositories</h4><p>Our repositories emit StateFlow to provide a stream of state updates for ViewModels. This ensures that the flow of data from the repository to the ViewModel and eventually the UI is seamless and efficient.</p><pre>class DssAuthIndexRepository : WebServicesManager.WebServiceCallback, RemoveServiceListenerCallback {<br><br>    private val _dssAuthIndexStateTOMutableStateFlow = MutableStateFlow(DssAuthIndexStateTO())<br>    val dssAuthIndexStateTOStateFlow = _dssAuthIndexStateTOMutableStateFlow.asStateFlow()<br><br>...</pre><h4>Screen State Defined with Sealed Interfaces</h4><p>To manage screen specific state, we use sealed interfaces. For example, the DssVehicleDetailsScreenState interface encapsulates the various states the screen can be in, helping to simplify state management and eliminate warnings.</p><pre>sealed interface DssVehicleDetailsScreenState : Serializable {<br>    data object LoadingTO : DssVehicleDetailsScreenState<br>    data class ContentTO(val dssVehicleDetailsContentTO: DssVehicleDetailsContentTO) : DssVehicleDetailsScreenState<br>    data class ErrorTO(var appMessages: Set&lt;AppMessage&gt; = mutableSetOf(), val vehicleDetailsErrorReason: VehicleDetailsErrorReason) : DssVehicleDetailsScreenState<br>}</pre><h4>ViewModels and StateFlow for Reactive Data Handling</h4><p>Our ViewModels use StateFlow to expose state updates to the UI. This helps keep the architecture reactive and ensures that the composables always reflect the latest data.</p><pre>class DssVehicleDetailsViewModel(private val uniqueVehicleKey: String, private val savedStateHandle: SavedStateHandle) : ViewModel() {<br><br>    val screenStateTOStateFlow = <br>        savedStateHandle.getStateFlow&lt;DssVehicleDetailsScreenState&gt;(KEY_SCREEN_STATE_TO, DssVehicleDetailsScreenState.LoadingTO)</pre><h4>Composables for Screen Composition</h4><p>Finally, our screen composables consume the ViewModel state and render the UI accordingly:</p><pre>val screenStateTO by viewModel.screenStateTOStateFlow.collectAsStateWithLifecycle()<br>...<br>when (screenStateTO) {<br>    DssVehicleDetailsScreenState.LoadingTO -&gt; {<br>        SfmaLoading(<br>            loadingConfigurationTO = <br>                LoadingConfigurationTO.LoadingWithDelayedTextConfigTO(stringResource(id = R.string.dss_landing_loading_label)),<br>        )<br>    }<br>    is DssVehicleDetailsScreenState.ContentTO -&gt; {<br>        DssVehicleDetailsScreenContent(<br>            scaffoldPaddingValues = scaffoldPaddingValues,<br>            contentTO = screenStateTO,<br>            onDiscountTapped = onDiscountTapped,<br>            onAddOdometerReadingTapped = onAddOdometerReadingTapped,<br>            onOrderNewBeaconTapped = onOrderNewBeaconTapped,<br>            onPairNewBeaconTapped = onPairNewBeaconTapped,<br>        )<br>    }<br>    is DssVehicleDetailsScreenState.ErrorTO -&gt; {<br>        when (screenStateTO.vehicleDetailsErrorReason) {<br>            VehicleDetailsErrorReason.DSS_AUTH_INDEX -&gt; onDssAuthIndexTechError()<br>        }<br>    }<br>}</pre><p>By embracing Jetpack Compose and MVVM, we modernized our Android development approach, resulting in a seamless and reliable Drive Safe &amp; Save integration within the State Farm Mobile app.</p><h3>iOS: Blending SwiftUI into a UIKit Legacy</h3><p>The iOS State Farm Mobile app has been around for some time now. Of course this means the app started out using UIKit for its user interface. Over time, as we updated our minimum supported iOS version (currently iOS 16) and gained experience with SwiftUI, we began integrating SwiftUI into the app. With the migration of the Drive Safe &amp; Save functionality into the State Farm Mobile app, one of the first decisions was where to place this functionality. Prior to the migration, we had 5 tabs: Overview, Insurance, Claims, Finances, and More. The More tab had little functionality so we decided to remove it and create a new tab called Safe &amp; Save for all of the new features.</p><h4>The Setup</h4><p>Around the time we started the Drive Safe &amp; Save migration we were also starting to break pieces of our code up into more manageable pieces. For this Drive Safe &amp; Save functionality we decided to create a target that would contain all of its functionality. While there are still lots of pieces of functionality in our main State Farm target, creating a new target allowed for overall better code organization.</p><p>For our UI related changes, we have an existing UITabBarController that was modified to include this new tab. Since our app is UIKit-based, we used UIHostingController. We created a DSSHostingController with content called DSSLandingView. This DSSHostingController lives in the State Farm target, allowing it to navigate to views in the State Farm target, such as the profile and preferences screen. For example, the following function is in DSSHostingController:</p><pre>func didTapProfileAndPreferences() {<br>   self.performSegue(withIdentifier: Segue.profileAndPreferences.identifier, sender: nil)<br>}</pre><h4>Managing State</h4><p>The DSSLandingView populates its content from an API call. There are multiple states a user could be in with their vehicles. State Farm is an insurance company that offers more than just auto products, so it&#39;s valid for a user to have no vehicles. A user can also have one or more vehicles, with some being eligible for Drive Safe &amp; Save, some enrolled, and some not eligible. In the view model, we have a published property representing this state as an enum:</p><pre>enum DriveSafeSaveLandingState {<br>   case determining<br>   case enrolled<br>   case notEligible<br>   ...<br>}</pre><p>And in the view:</p><pre>var body: some View {<br>   switch state {<br>   case .determining:<br>      EmptyView()<br>   case .enrolled:<br>      EnrolledView()<br>   case .notEligible:<br>      NotEligibleView()<br>   }<br>}</pre><h4>Navigation</h4><p>Another item SwiftUI makes extremely easy to handle is navigation. We used <a href="https://developer.apple.com/documentation/swiftui/view/navigationdestination(for:destination:)">.navigationDestination</a> in places we need to push on views. For example:</p><pre>.navigationDestination(for: ProfileDestination.self) { destination in<br>   switch destination {<br>   case .communicationSettings:<br>      DSSCommunicationSettingsView()<br>   case .contactUs:<br>      DSSContactUsView()<br>   case .helpTopics:<br>      FAQTopicsView()<br>   case .aboutTheApp:<br>      AboutTheAppView()<br>   case .profilesAndServices:<br>      ProgramsAndServicesView()<br>   }<br>}</pre><p>This integration of a new SwiftUI tab into our existing UIKit-based iOS app allowed us to deliver Drive Safe &amp; Save features with a modern, flexible user interface, all while maintaining seamless navigation and a consistent user experience within the State Farm Mobile app.</p><h3>Improving the User Experience: Before and After</h3><p>Merging Drive Safe &amp; Save into the State Farm Mobile app was never just about reducing the number of apps on a user’s phone — it was about making every interaction simpler, more intuitive, and more valuable.</p><p><strong>Before the migration:</strong></p><ul><li>Users who wanted to enroll in Drive Safe &amp; Save or view their telematics data needed to download, log in to, and manage a separate app.</li><li>Many State Farm customers were unaware of Drive Safe &amp; Save, or missed out on features like trip feedback and Accident Assistance simply because they weren’t using both apps.</li><li>Switching between apps to manage policies, pay bills, and access telematics features created friction and increased the likelihood of missing important information.</li></ul><p><strong>After the migration:</strong></p><ul><li>Everything is in one place: users can enroll in Drive Safe &amp; Save, access driving feedback, manage policies, pay bills, and start claims all from the State Farm Mobile app.</li><li>Drive Safe &amp; Save features are now more prominent and accessible, leading to increased enrollments and engagement.</li><li>The migration flow was carefully designed to ensure users didn’t lose access to critical features like trip recording and Accident Assistance, so the transition felt seamless.</li><li>Unified navigation and consistent UI patterns make it easier for users to discover and use new features.</li><li>With fewer apps to juggle, users have a more streamlined, reliable, and satisfying State Farm experience.</li></ul><p>By bringing everything together under one app, we’ve not only simplified the customer journey, but also set a new baseline for what users can expect from their State Farm app going forward.</p><h3>Lessons Learned: What Worked and What We’d Do Differently</h3><p>No major migration comes without a few surprises. Along the way, we encountered unexpected challenges, uncovered opportunities for smarter solutions, and learned valuable lessons about both engineering and project management. In this section, we’ll highlight a few of the key insights and takeaways that will help guide us in future efforts.</p><h4>Geocoding at Scale: How We Turbocharged the Trips List with Smart Caching</h4><p>One of the most interesting technical challenges we tackled during the Android migration was optimizing the performance of our Trips Landing screen. This screen displays all trips taken by users and other drivers on their policy over the past 30 days. For larger households, this can mean well over 100 trips — each with its own data to load.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/298/1*knAxD7eCQTy4JCqqtI1zOw.png" /></figure><p>A key piece of information we display for each trip is the destination city, which we derive by reverse geocoding the trip’s ending latitude and longitude using Android’s Geocoder class. While the destination is being fetched, we show a loading state in place of the city name.</p><h4>The Problem: Geocoder Bottlenecks</h4><p>When displaying a large number of trips, we initially encountered performance issues related to reverse geocoding each destination in real time. This led to slow loading times for users with extensive trip histories, in part due to external service rate limits and caching behaviors.</p><h4>The Solution: Lazy Loading and Smarter Caching</h4><p>Credit goes to fellow engineer <a href="https://www.linkedin.com/in/exampledotcom/">Andrew Erickson</a>, who devised an innovative two-part solution that made the Trips Landing screen performant:</p><p><strong>1. On-Demand Geocoding</strong></p><p>Rather than processing every trip’s destination at once, we now trigger geocoding only for destinations that are likely to be viewed soon. This reduces unnecessary processing and network calls, especially when users scroll quickly through their trip history.</p><p><strong>2. Optimized Caching</strong></p><p>We enhanced our caching mechanisms to better handle repeat destinations and minor GPS variations. By grouping similar locations and leveraging in-memory storage, we minimize redundant geocoding requests and improve response times.</p><h4>The Impact</h4><p>Thanks to Andrew’s combination of on-demand loading and optimized caching, we slashed unnecessary Geocoder calls and cut down on loading times — even for users with very large trip histories. Users now see their trip destinations populate quickly, and the Trips Landing screen remains fast and responsive.</p><p>Optimizing destination city loading on the Trips Landing screen was a great example of how thoughtful engineering and innovative solutions turned a sluggish feature into one that feels seamless for users. These improvements translate directly to a more polished and reliable experience for our users.</p><h4>Reducing Friction in Permission Handling</h4><p>For Drive Safe &amp; Save to work correctly, the Android version of the app needs several permissions from the user during onboarding. Shortly after release, we noticed a high drop-off rate on the location permission screen. We suspected that users may be downgrading the location permission from the settings screen, for example, choosing “Don’t allow”, then switching back to “Allow all the time.” On Android, this will trigger the OS to kill the app process.</p><p>The State Farm Mobile app’s security logic returns users to the login screen after a process death. This meant that when users downgraded a permission, that required users to log in again and navigate back to the Drive Safe &amp; Save tab, creating a major friction point.</p><p><strong>The Solution: Restore Sessions After Permission Downgrades</strong></p><p>We added logic to detect permission downgrades and, when possible, restore the user’s authenticated session. This allowed users to pick up where they left off without having to log in again.</p><p>These changes led to an improvement in onboarding completion rates. Monitoring analytics and implementing Firebase non-fatal events helped us quickly identify and confirm the root cause of the drop-off, reinforcing the value of closely tracking critical user flows.</p><h4>Estimating the Unknown is Hard</h4><p>At the outset of the migration, we underestimated just how challenging it would be to predict our delivery timeline. Our initial approach to project management was rough around the edges — we were dealing with shifting requirements, new technical hurdles, and the complexity of coordinating two platforms. As a result, our story tracking and estimation lacked the rigor and clarity needed for a project of this scale.</p><p>After a few sprints of missed estimates and unclear progress, we realized we needed a better system. We invested in more disciplined story management: breaking down work into smaller, well-defined stories, setting clearer acceptance criteria, and regularly updating progress. We improved communication between engineers, product owners, and stakeholders to ensure everyone had a shared understanding of priorities and blockers.</p><p>With improved visibility into our backlog and progress, we could finally provide more accurate timelines. This new level of transparency also made it easier to make the case for bringing on additional engineering talent — helping us stay on track and meet our objectives.</p><p>The lesson: big migrations demand more than just technical skill — they require intentional, evolving project management practices to keep everything moving forward.</p><h4>Need for Continuous Regression Testing</h4><p>As development progressed, many stories impacted the same areas of the app’s codebase. After several sprints, both testers and engineers occasionally discovered that features completed in previous sprints had defects. This highlighted the need for a plan to maintain the quality of previously completed work throughout the project.</p><p>To address this, our testing team committed to ongoing regression testing for the duration of the migration effort. Each sprint, they revisited and validated features from previous sprints to ensure that recent changes had not introduced new issues. This continuous regression testing helped us catch and resolve defects early. It was easier for us engineers to resolve defects that were introduced recently.</p><p>This proactive approach to regression testing ensured that quality remained a top priority throughout the migration. By continuously validating previous work, we minimized the risk of defects slipping through and preserved high quality as new features were implemented.</p><h3>The Results: Adoption, Stability, and Satisfaction</h3><p>Millions of users have successfully migrated from the standalone Drive Safe &amp; Save app to the State Farm Mobile app. This seamless transition has resulted in a significant increase in app adoption and engagement, with more users exploring features and returning to the app regularly. Here’s a look at the impact so far:</p><ul><li><strong>User Growth:</strong> Since the migration began, the State Farm Mobile app has seen an approximate 20% increase in active users.</li><li><strong>Drive Safe &amp; Save Enrollments:</strong> Monthly Drive Safe &amp; Save mobile app initiated enrollments have doubled.</li><li><strong>Drive Safe &amp; Save Trip Recording:</strong> Currently, 85% of all Drive Safe &amp; Save trips are now recorded through the State Farm Mobile app instead of the legacy app. This number is projected to reach over 90% as more users complete the transition.</li><li><strong>Accident Assistance:</strong> Enrollments doubled.</li><li><strong>Exceptional Stability:</strong> Despite the complexity of the migration and the increase of new users, the app continues to deliver a crash-free experience, with an average crash-free rate of 99.98% on both platforms.</li><li><strong>Customer Satisfaction:</strong> Across both Android and iOS, the app maintains an impressive average customer satisfaction rating of 92.6%.</li></ul><h3>The Road Ahead: Expanding Telematics</h3><p>This migration effort was a large team effort involving collaboration across numerous teams at State Farm. Engineers, designers, product owners, testers, and other stakeholders all worked together to achieve this milestone. For both of us, being part of such a successful and collaborative effort has been one of the highlights of our careers.</p><p>As proud as we are of this achievement, we know this is only the beginning. The integration of telematics into the State Farm Mobile app opens up exciting new possibilities for innovation. We’re just scratching the surface of what telematics can do to empower users, help improve driving habits, and enhance safety. The future is bright for telematics in the State Farm Mobile app, and we’re ready to continue driving forward!</p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c69b29cdf03f" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/unifying-our-mobile-experience-how-state-farm-integrated-telematics-into-its-flagship-app-c69b29cdf03f">Unifying Our Mobile Experience — How State Farm Integrated Telematics Into Its Flagship App</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[DevSecFinOps: The Challenge of Implementing a Secure and Cost-Effective Container-Based CI/CD…]]></title>
            <link>https://engineering.statefarm.com/devsecfinops-the-challenge-of-implementing-a-secure-and-cost-effective-container-based-ci-cd-c2257eac8eb4?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/c2257eac8eb4</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[education]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[gitlab]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Thu, 17 Jul 2025 15:01:31 GMT</pubDate>
            <atom:updated>2025-07-17T15:01:31.405Z</atom:updated>
            <content:encoded><![CDATA[<h3>DevSecFinOps: The Challenge of Implementing a Secure and Cost-Effective Container-Based CI/CD System</h3><p>By <a href="https://www.linkedin.com/in/edward-northcutt-b06386101">Eddie Northcutt</a> and Lane Leake</p><h3>Introduction</h3><p>Running CI/CD at scale can feel like juggling on a unicycle; it’s all about balance. One misconfiguration and everything comes crashing down: timeouts, long build times, and unhappy engineers. At State Farm, we process millions of CI/CD jobs each month with a self-managed GitLab and GitLab CI/CD implementation. In this article, we’ll share how we do it using a combination of custom GitLab runners leveraging containers with the Sysbox runtime, and EC2 auto-scaling.</p><h3>DevSecFinOps: The Challenge of Implementing a Secure and Cost-Effective Container-Based CI/CD System</h3><p>When you’re pushing thousands of commits a day and each commit triggers multiple pipelines, high concurrency is just the first challenge. We also need:</p><ul><li><strong>Fast Feedback:</strong> Engineers shouldn’t have to wait hours for a build to complete or for their job to even start.</li><li><strong>Security &amp; Isolation:</strong> Each build environment must be ephemeral and isolated to protect against malicious code or accidental resource hijacking.</li><li><strong>Cost Optimization:</strong> At millions of jobs per month, even slight inefficiencies can multiply into major bills.</li><li><strong>Flexibility:</strong> Engineers should be able to rapidly develop prototypes based on new technology and not be constrained by CI/CD infrastructure.</li></ul><p>Given all these requirements, it may seem like we’re trying to have our cake and eat it too, however, that’s simply the reality of modern software development at scale. GitLab Runner supports a variety of compute engines (executors), and some of these allow us to use containers, which greatly increases the ease of supporting additional software stacks. Containers also introduce new layers of complexity and vulnerabilities that must be addressed.</p><h3>Our Architecture in a Nutshell</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*x56-bhK7vFKiQaMtoOPHjw.png" /></figure><p>While this simplified diagram might seem like a lot of moving parts for running some containers, continue reading to learn about why this is more challenging then it might appear</p><h3>Challenge # 1: Container Security and Isolation</h3><p>Security is a critical aspect of modern software development, especially when it comes to containerization. Honestly, it’s really hard to get right. One of the significant challenges we faced was a container escape vulnerability identified during penetration testing (pen test). The Pen Test team successfully executed a Docker container escape, gaining access to the root EC2 system. This incident highlighted the urgent need for enhanced security measures in our containerized environments. Ultimately, the issue was due to the fact that we were creating privileged containers in order to run Docker-in-Docker (DinD) for our GitLab runners. While this is a common pattern, using DinD on shared infrastructure poses significant security risks.</p><p>Traditionally, your only options to solve this problem were to use a privileged container, bind mount the host’s Docker socket (equally insecure), migrate to virtual machines and manage those, or use a tool like Kaniko to build images without DinD. However, these solutions either compromise security, limit functionality, or require significant changes to existing workflows. All things we wanted to avoid.</p><h3>Thinking Outside the Box with Sysbox</h3><p>To address this critical finding, we implemented <a href="https://github.com/nestybox/sysbox">Sysbox</a>. If you haven’t heard of Sysbox, it’s an open-source container runtime developed by Nestybox (now acquired by Docker) that enhances container isolation by utilizing Linux user namespaces and virtualizing portions of procfs and sysfs. This allows containers to run system-level software seamlessly and securely. Acting as a &quot;container supercharger,&quot; Sysbox enables existing container managers and orchestrators to deploy containers with hardened isolation; without requiring modifications to workflows or images all while coexisting with other container runtimes on the same host.</p><p>In other words, it allows containers to run workloads typically reserved for virtual machines, without compromising security. Ultimately, it enables the deployment of our Docker-in-Docker setups without requiring privileged containers, mitigating potential security risks.</p><h3>Playing Nice in the ~Sand~Sysbox</h3><p>Thankfully, this works quite well out of the box with GitLab Runner. All we need to do is configure the runner’s config.toml to use the Sysbox runtime by ensuring the runners.docker.runtime is set to sysbox-runc. Below is an example configuration that illustrates how to set up the GitLab runner with Sysbox:</p><pre>concurrent = 1<br>check_interval = 0<br>connection_max_age = &quot;15m0s&quot;<br>shutdown_timeout = 0</pre><pre>[session_server]<br>  session_timeout = 1800</pre><pre>[[runners]]<br>  name = &quot;runner-01234&quot;<br>  unhealthy_interval = &quot;15m0s&quot;<br>  url = &quot;https://private-instance.gitlab.com/&quot;<br>  # ...rest of the configuration...<br>  executor = &quot;docker&quot;<br>  [runners.cache]<br>    Type = &quot;s3&quot;<br>    Shared = true<br>    MaxUploadedArchiveSize = 0<br>    [runners.cache.s3]<br>      AccessKey = &quot;[REDACTED]&quot;<br>      SecretKey = &quot;[REDACTED]&quot;<br>      BucketName = &quot;[REDACTED]&quot;<br>  [runners.docker]<br>    runtime = &quot;sysbox-runc&quot;<br>    privileged = false<br>    # ...rest of the configuration...</pre><p>When this is set, the GitLab runner starts up the job in a system container powered by Sysbox, granting it specific capabilities that allow it to spin up an inner container image running a Docker daemon. Very similar to a Docker-in-Docker setup. Our build container then interacts with this Docker daemon, enabling us to continue using Docker without sacrificing security, all while further enhancing the isolation between the EC2 host and the job container. As a bonus, we were able to make this change without requiring any modifications to our user’s existing CI/CD pipelines, which is a huge win for all of us.</p><h3>Challenge # 2: Slow EC2 Auto-Scaling</h3><p>While Sysbox solved our security challenges, we still faced performance issues with our EC2 auto-scaling setup. Engineers expect automated pipelines to be fast, efficient, and reliable. Waiting for infrastructure to be provisioned can be a bottleneck, as each time a new EC2 host is scaled up it needs to run through the initial cloud-init process. In our observations, this added nearly 3–5 minutes of job time in the worst cases. Additionally, these machines are cordoned off after 20 jobs to ensure reliability, meaning that the process of creating a new machine is repeated frequently.</p><h3>Pulling ourselves up by our bootstraps</h3><p>To solve this problem, we created a custom AMI that is pre-loaded with all the tools and configuration needed to start the machine. Now, our EC2 instances can be provisioned in under 60 seconds. This results in a significant improvement over the previous 180–300 seconds.</p><p>This change has resulted in a substantial reduction in pipeline job durations and wait times, allowing developers to focus more on coding and less on waiting. Additionally, provisioning is more reliable, as dependencies are bundled with the AMI and we ensure each machine is created using the same tooling.</p><h3>Time is Money</h3><p>As some of you know all too well, part of the EC2 pricing is based on the time the instance is running, so this change has also resulted in a decrease in our AWS spending. We estimate that this improvement saves approximately 187,200 compute minutes each month and 6,240 minutes each day.</p><p>With infrastructure spin-up times slashed, we could finally turn our attention to another major operational concern: controlling the ballooning costs and performance impacts associated with networking and container image management.</p><h3>Challenge # 3: Networking Costs</h3><p>While the previous challenges were primarily focused on security and performance, we also had to address the cost of networking. With millions of CI/CD jobs running each month, the data transfer costs can add up quickly. To throw salt in the wound, many engineering teams fall into the trap of creating “Swiss army knives” for their CI/CD needs, resulting in Docker images that are often larger than necessary. This not only increases the complexity of the build process but also leads to significant challenges: AWS Network Data Transfer fees, performance issues from pulling large images, and poor network design relying on NAT Gateways to reach Docker registries.</p><h3>Lean, Mean, CI/CD Machines</h3><p>To solve these problems, we encouraged the optimization of GitLab CI/CD images and categorized them into build-time and runtime images. Encouraging build-time images to be single purpose, as well as promoting the use of reusable CI/CD components with very specific uses has improved CI/CD image sizes and reduced the number of “One-Image-To-Rule-Them-All” images. We also created VPC Endpoints to one of our SaaS container registry providers, which dramatically reduced the cost of our NAT Gateway expenses and even improved network performance when retrieving public images.</p><h3>Cache in the Bank: Custom Docker Registry Proxy</h3><p>While these changes helped, they didn’t have the impact we were hoping for. Given the container-first approach of our CI/CD solution, another challenge is retrieving and storing container images used in jobs. Additionally there is the network cost associated with pulling these images from external registries, especially when images need to be pulled frequently due to the ephemeral nature of the machines. AWS NAT Gateway costs are no joke and, at scale, add up quickly. To address this, we implemented a Custom Docker Registry Proxy that caches frequently accessed images pulled from our private GitLab container registry, reducing the need for repeated data transfers and minimizing costs for our most commonly pulled images.</p><p>Our implementation of this solution is inspired by the great work @rpardini has done with the <a href="https://github.com/rpardini/docker-registry-proxy.">Docker Registry Proxy project</a>. This solution acts as a man-in-the-middle (MitM) intercepting proxy based on Nginx, positioned between the GitLab Shared AWS Runners and the primary GitLab container registry housing CI/CD images used at State Farm.</p><p>Once a request to the GitLab registry is intercepted, we cache large blob/layer requests, which tend to incur significant latency and data transfer costs. Future requests for the same blob/layer are served from the cache, reducing the need to transfer data from upstream registries. We do not cache manifests, as that allows us to see if the image has changed and ensure we are only pulling blobs that we do not already have cached.</p><p>One of the benefits of our implementation over the project’s is that GitLab’s access controls are still enforced. If a pipeline user lacks permission to access a Docker registry image, they will receive a 403 error, even if the image is available in the cache. As a bonus, it allows us to control what registries are proxied and cached and which are not, so we can avoid caching images from registries that we do not want to cache, such as internal Elastic Container Registries.</p><p>After this solution had some time to bake in and the cache had been populated, we saw a significant reduction in the number of requests made to the GitLab registry. This has led to a substantial decrease in our AWS NAT Gateway costs, as well as improved performance for our CI/CD pipelines. The Custom Docker Registry Proxy has become an essential component of our CI/CD infrastructure, allowing us to efficiently manage container images while keeping costs under control. Most recently we are seeing:</p><ul><li>~93% cache hit rate</li><li>~25–30TB of data served per day from the cache</li></ul><p>This solution has not only helped us slash our network costs, but also improved the speed of our CI/CD pipelines by reducing the time it takes to pull container images. By caching frequently accessed CI/CD images, we have minimized the networking hops required for pulling a Docker image for GitLab pipeline jobs.</p><h3>Conclusion</h3><p>Operating CI/CD at scale is a continuous journey of balancing security, performance, and cost; each decision introducing new considerations and opportunities for innovation. At State Farm, we’ve architected a solution that leverages containerization, advanced runtimes like Sysbox, custom AMIs, and network optimizations to create a robust, secure, and highly performant CI/CD ecosystem. Along the way, we’ve encountered and solved real-world challenges that many organizations face as they scale their development pipelines.</p><p>Our experience has shown that success at scale isn’t about a single tool or breakthrough, but about layering solutions that reinforce each other. By focusing on secure isolation with Sysbox, speeding up infrastructure provisioning with custom AMIs, and reducing network and storage costs through caching and image optimization, we’ve been able to deliver a developer experience that is both agile and sustainable.</p><p>As CI/CD requirements continue to evolve, so too will our architecture. We’re excited to keep exploring new ways to empower our engineering teams while keeping security and costs in check. We hope our journey inspires others facing similar challenges, and we welcome your thoughts, questions, and war stories in the comments below.</p><h3>References</h3><ul><li><a href="https://github.com/nestybox/sysbox">Sysbox: A Secure Container Runtime for System Containers</a></li><li><a href="https://docs.gitlab.com/runner/">GitLab Runner Documentation</a></li><li><a href="https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/">GitLab Runner Auto-Scaling on AWS</a></li><li><a href="https://github.com/rpardini/docker-registry-proxy">@rpardini’s Custom Docker Registry Proxy GitHub Repo</a></li></ul><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c2257eac8eb4" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/devsecfinops-the-challenge-of-implementing-a-secure-and-cost-effective-container-based-ci-cd-c2257eac8eb4">DevSecFinOps: The Challenge of Implementing a Secure and Cost-Effective Container-Based CI/CD…</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Rendering Equation: Client + Server + Framework]]></title>
            <link>https://engineering.statefarm.com/the-rendering-equation-client-server-framework-aca0018832b6?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/aca0018832b6</guid>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ui]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Mon, 19 May 2025 14:36:30 GMT</pubDate>
            <atom:updated>2025-05-19T14:36:30.796Z</atom:updated>
            <content:encoded><![CDATA[<p>By <a href="https://www.linkedin.com/in/jordan-leeper-bb817b42">Jordan Leeper</a></p><h3>Introduction</h3><p>When I first started my career 12+ years ago, I had no idea how client-server interactions worked. I often see new software engineers that have learned a JavaScript framework in college or in a bootcamp lacking some critical understanding of how their frontend application interacts with its API(s). I wish that I could have had someone explain to me some of those basic concepts, like what is client-side rendering (CSR)? What is server-side rendering (SSR)? How do they work together? How do they work for different frameworks? Hopefully after reading this, you will feel confident in answering these questions!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/612/1*E0BX8ZNyLagISKQQOoLb7A.jpeg" /></figure><h3>The Basics</h3><p>Regardless of CSR or SSR, the browser interacts with the server the same way.</p><p>Here’s a high-level breakdown:</p><ol><li>The client (browser) sends a GET request to the server (for an application)</li><li>The server responds with an HTML file</li><li>The client (browser) requests any additional assets provided in the HTML file with &lt;link&gt; or &lt;script&gt; elements</li><li>The server responds with the requested assets.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*gfL169gM3nlGmoRUFGYZSQ.png" /></figure><p>Steps 1 and 2 are done for every single application on the web. Steps 3 and 4 are optional (depending on your framework) and can be repeated many times depending on how much additional content the application might need to load.</p><p>The key difference between CSR and SSR is in step 2 — the HTML that is returned by the server. Let’s get into the details.</p><h3>Client-side Rendering (CSR)</h3><p>CSR is the process by which the client, in our case the browser, creates the views as it is instructed by JavaScript.</p><p>Any UI that loads HTML and then attaches elements to the Document Object Model (DOM) in the browser is doing a type of client-side rendering. Even vanilla JavaScript that appends and manipulates the browser’s DOM is an implementation of client-side rendering.</p><p>Nowadays, we usually think about frameworks like React, Vue, Angular, Svelte, Solid, etc. when it comes to CSR.</p><p>The actual HTML that is returned is almost always very sparse and has a single element that is used as the starting point for building out the views using JavaScript. These CSR frameworks look for that starting element, such as a div with an id, to build out the UI within.</p><p>For example, here is the starting HTML file for an Angular application that is passed to the browser.</p><pre>&lt;!doctype html&gt;<br>&lt;html lang=&quot;en&quot;&gt;<br>&lt;head&gt;<br>  &lt;meta charset=&quot;utf-8&quot; /&gt;<br>  &lt;title&gt;My App&lt;/title&gt;<br>  &lt;base href=&quot;/&quot; /&gt;<br>  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&gt;<br>  &lt;link rel=&quot;icon&quot; type=&quot;image/x-icon&quot; href=&quot;favicon.ico&quot; /&gt;<br>  &lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot; /&gt;<br>&lt;/head&gt;<br>&lt;body&gt;<br>  &lt;app-root&gt;&lt;/app-root&gt;<br>  &lt;script src=&quot;polyfills.js&quot; type=&quot;module&quot;&gt;&lt;/script&gt;<br>  &lt;script src=&quot;main.js&quot; type=&quot;module&quot;&gt;&lt;/script&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;</pre><p>Almost no HTML elements exist in the file, because it is all built out at runtime when the browser loads the application’s JavaScript. As you can see, the &lt;script src=&quot;main.js&quot; type=&quot;module&quot;&gt;&lt;/script&gt; tag will run to load the JavaScript which constructs the views for the application. This is true for every CSR framework that dynamically creates HTML via JavaScript in the browser.</p><p>CSR is often very appealing because of the abundance of frameworks available and how easy they are to use. Generally, they manage routing in the browser instead of going back and forth to a server to generate HTML. They also provide an excellent developer experience when paired with tool like <a href="https://vitejs.dev/">Vite</a> that can manage hot reloading and file bundling. Another advantage of a CSR framework is that the final result of the build is a set of static files that don’t need to be run on a server. This makes deploying and managing a UI as simple as uploading files to cloud storage rather than managing containers or servers.</p><h3>Server-side Rendering (SSR)</h3><p>Server-side rendering is when the view (HTML) is created by the server and then sent to the client (browser).</p><p>When it comes to SSR, this is the tried-and-true experience that many web developers have used since the beginning. The browser makes an HTTP GET request for a file and the server returns a fully built out HTML file filled with elements that the browser uses. That HTML page may sometimes use some client-side JavaScript for things like form validation, animations, reactivity, and occasionally adding an element to the UI, but generally most of the HTML is already there and was provided by the server. Things like Spring JSP, Node + EJS, .NET Razor, and PHP are probably some frameworks that come to mind.</p><p>These days, most front-end developers don’t actually mean those frameworks when talking about SSR. They are usually referring to frameworks that combine both CSR and SSR together. For our context, let’s consider SSR to refer to frameworks that provide those capabilities like Next.js or Nuxt or SvelteKit.</p><p>So, what is it then?</p><p>Basically, an SSR framework allows you to render the HTML on a server and send it to the client so that the content renders immediately. Then, the client-side part of the framework (such as React, Vue, etc.) kicks in. Now you are now able to take advantages of your CSR framework’s features such as event handling and easy ways to template your UI. Let’s keep digging in!</p><h3>SSR Frameworks</h3><p>These SSR frameworks allow you to use the CSR framework/library you might be familiar with, but also take advantage of all of the features of SSR!</p><ul><li><a href="https://nuxt.com/">Nuxt (Vue)</a></li><li><a href="https://nextjs.org/">Next.js (React)</a></li><li><a href="https://waku.gg/">Waku (React)</a></li><li><a href="https://kit.svelte.dev/">SvelteKit (Svelte)</a></li><li><a href="https://docs.solidjs.com/solid-start">SolidStart (Solid)</a></li></ul><p>Other frameworks that offer SSR built in are:</p><ul><li><a href="https://angular.dev/guide/ssr/">Angular</a></li><li><a href="https://astro.build/">Astro</a></li></ul><p>Each of these frameworks offer different pros and cons and are at different stages in terms of what features they offer. For example, at the current time of writing, only Next.js offers a fully robust <a href="https://nextjs.org/docs/pages/building-your-application/data-fetching/incremental-static-regeneration">Incremental Static Regeneration</a> capability. This can be set up manually with other frameworks, but requires more overhead.</p><p>With Angular, for example, enabling SSR takes only one CLI command to add to your existing non-SSR Angular project: ng add @angular/ssr. The advantage here is that you don&#39;t have to migrate your CSR application to a completely different framework to enable SSR like you would need to do with React to Next.js or Vue to Nuxt.</p><h3>The CSR + SSR Problem</h3><p>Like I mentioned earlier in the post, CSR frameworks look for an element in the HTML and then mount onto it. They will start to append and build out the HTML via JavaScript. When this happens with an already fully created HTML view that was provided via SSR, the HTML would actually get destroyed and then re-created. This results in a flash of the page as the content is visible for a split second, before it’s destroyed by the JavaScript and then added back.</p><h3>The CSR + SSR Solution = Hydration</h3><p>How can we fix this page flashing? The answer is something you’ve probably heard about. Hydration!</p><p>Hydration, while complex to implement, is actually quite simple to understand. Rather than the CSR framework destroying the already rendered HTML elements, let’s provide them to the framework instead! JavaScript often needs to apply interactivity to the HTML by attaching event listeners to buttons, the window, or other elements. This is where the concept of hydration comes from, since the page is not interactive until it’s been “watered” by the JavaScript that has been loaded. This also prevents the page from flashing, since the CSR framework doesn’t rebuild the HTML elements.</p><h3>What advantages does SSR have?</h3><p>SSR still plays a very crucial role in front-end development. While React, Angular, and Vue might be sufficient for most UI applications, there are several gaps that SSR fills.</p><ol><li>SSR applications can often provide improved performance when it comes to <a href="https://developer.mozilla.org/en-US/docs/Glossary/First_contentful_paint">first contentful paint (FCP)</a> since the browser does not <em>need</em> to download JavaScript in order to display the views.</li><li>When building out complex and performance heavy visuals or elements, it can often be faster to do so on the server which generally has more powerful resources available than a user’s browser.</li><li>Search Engine Optimization (SEO) <em>was</em> a very common use case, but perhaps not anymore. It was generally thought that search engine crawlers that traversed web pages to index them had a lot of problems when it comes to JavaScript heavy (CSR) pages since they might not wait for the view to be constructed (among other things). However, <a href="https://vercel.com/blog/how-google-handles-javascript-throughout-the-indexing-process">this article from Vercel</a> demonstrates with evidence from over 37k different HTML pages that JavaScript heavy pages may no longer have as many issues when it comes to Google’s page indexing and SEO processing (At least for the Googlebot crawler).</li></ol><h3>Infrastructure</h3><p>Running an SSR framework locally is usually very easy. The complex part comes in the big differences required when deploying to a test/QA/production environment. The standard approach for a CSR app is to use something like Amazon S3, Cloudflare Pages, etc. that acts as a simple file store since we don’t actually need a server. However, this is no longer possible with SSR (due to that Server which needs to construct the HTML per request) and we can’t just rely on static content being delivered via cloud storage.</p><p>Many providers offer capabilities that can help simplify SSR infrastructure management. Vercel offers SSR capabilities for Next.js, SvelteKit, Nuxt, and Astro. <a href="https://hub.nuxt.com/">NuxtHub</a> offers full stack capabilities for Nuxt applications. AWS has several services that could enable SSR applications such as Amazon EKS, Amazon ECS, or AWS Amplify. Firebase offers App Hosting for Next.js and Angular applications that leverages Google Cloud Platform behind the scenes.</p><p>One important note is that using a cloud storage solution to serve static content, such as a CSR app, is generally cheaper than running a server which hosts an SSR application. It is also often much simpler to set up and maintain.</p><h3>Other acronyms?</h3><p>Oftentimes, several other concepts are talked about when it comes to SSR and I think it’s critical to understand what they are. SSR occurs at runtime. When a web request reaches the server, it will build out the HTML response dynamically and send it back to the client. Sometimes we might want to build out the HTML ahead of time since it won’t change per request. This is where Static Site Generation (SSG) and Incremental Static Regeneration (ISR) come into play. Many SSR frameworks offer these capabilities so that it will lessen the load on the server and improve efficiency.</p><h3>Static Site Generation (SSG)</h3><p>Static site generation is when the associated pages of your application are pre-rendered at build time rather than dynamically when requested by the server. Many frameworks are able to do both SSR and SSG in the same application! Angular does this out of the box. This can be a very efficient approach since all of the work is done up front. However, it can be cumbersome to easily make changes to content since it is all done at build time.</p><h3>Incremental Static Regeneration (ISR)</h3><p>ISR allows your applications to periodically regenerate pages that were created via SSG. This means that you no longer need to do a redeploy and rebuild of your statically generated pages to pick up new content as long as the content is pulled dynamically from a source, such as a content management system (CMS), at build time. Not all frameworks offer ISR, but it is becoming more popular.</p><h3>What does State Farm do?</h3><p>In general, State Farm enables teams to use the tools which best fit the problems they are trying to solve for. This means that each of our applications have the potential to be built with different frameworks depending on the team’s knowledge, the product area, and the different ways we need to interact with customers. State Farm has a large number of UI frameworks in use including Angular, Astro, Ember, Next.js, React, Vue, Spring MVC + JSP, etc.</p><p>Some of the common patterns I recommend to our engineering teams are:</p><ul><li>Do you have a form heavy application with a lot of inputs? Save yourself some time and use a framework that enables two-way data binding like Vue or Angular.</li><li>Spinning up a quick project or proof of concept? React might be an easy option since it has many learning resources and open-source dependencies.</li><li>Building a large, enterprise application with many engineers and you need to have an easy time managing updates and technical debt? Use Angular. It prioritizes backwards compatibility and easy version updates.</li><li>Do you need to build a UI where first page load speed is super critical? Use an SSR framework like Next.js or Angular with SSR.</li></ul><h3>Review — when to use CSR or SSR?</h3><p>This is a question a lot of teams ask me, but I’m hopeful that now that we have more of a grasp on their differences, we can determine what makes the most sense to use. Let’s review.</p><h3>When to use SSR</h3><ul><li>You have an application that needs to display as soon as possible. We can achieve this by sending pre-rendered HTML to the browser and not needing to load JavaScript to display the UI.</li><li>Your application needs to maximize its SEO statistics. While the aforementioned article from Vercel is promising, our best bet is to keep things easy for web crawlers and send all of our content on the first load of the HTML.</li><li>Your application needs to do heavy data processing which might cause very slow load times or interactions in the client. Process the data on the server rather than relying on the browser.</li></ul><h3>When to use CSR</h3><ul><li>For most other scenarios, using CSR is the right choice. It’s cheaper to host, easier to manage the infrastructure, and makes for a great user experience.</li></ul><h3>Exceptions…</h3><ul><li>One scenario where it might make sense to use something other than CSR would be for certain types of pages that don’t require much user data or interactivity. For example, marketing pages that display static content. In this scenario, you might want to consider using SSG. Oftentimes JavaScript frameworks such as Angular and Astro can let you choose what routes of your application can be pre-rendered at build time.</li><li>Another scenario might be a need for a limited backend API or Backend for Frontend (BFF). By creating an SSR application, you could also include one or more API routes that the client calls. However, this can grow complicated. As API needs grow and change, you should be cognizant of when pulling those capabilities into their own managed service makes more sense.</li></ul><h3>Conclusion</h3><p>Understanding client-server requests and how your framework operates helps teams with debugging, building efficient applications, and providing the best experience possible to users. Those basics, along with an understanding of CSR vs SSR, empowers teams to craft their ideal application. Now that you’ve had a chance to learn more you can ask yourself: Do I need SSR for my application or will CSR suffice? Can my application’s content be pre-rendered with SSG? Is the cost of running an SSR application server worth it versus a CSR application hosted in static cloud storage? Thanks for reading and let’s get building!</p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aca0018832b6" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/the-rendering-equation-client-server-framework-aca0018832b6">The Rendering Equation: Client + Server + Framework</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Level Up Your Design Practices]]></title>
            <link>https://engineering.statefarm.com/level-up-your-design-practices-0f328abda9cc?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/0f328abda9cc</guid>
            <category><![CDATA[architecture]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[design]]></category>
            <category><![CDATA[software-architect]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Mon, 21 Apr 2025 19:16:37 GMT</pubDate>
            <atom:updated>2025-04-21T19:16:37.517Z</atom:updated>
            <content:encoded><![CDATA[<p>By <a href="https://www.linkedin.com/in/ben-justick-64b83b78">Ben Justick</a></p><h3>Introduction</h3><p>As an Architect, I’m always thinking about how to articulate a technical design or architectural change that’s in my head in a way that it can be easily consumed and understood to accelerate engineering efforts. When you’ve been doing this for a long time, the most effective design techniques and practices almost become like second nature. However, I’ve found that less experienced engineers and architects often struggle with articulating their design in a way that others on the team can understand. Typically, the right pieces are there, but they are organized in a way that doesn’t complete the puzzle. In this post, I intend to cover the approach and best practices I use in context to practically every architecture and design assignment. My hope is that you gain a practical guide to design that you can start using on your next assignment that leaves your team impressed with the end result.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/612/1*c7CX1RhA9D10q6DJQjqlQw.jpeg" /></figure><h3>Step 1 — Do Your Homework</h3><p>When you first get a design assignment, there’s a very strong possibility that you are going to want to immediately start drawing up design diagrams or creating design documentation. This is something I’m often guilty of myself, especially if I’m excited about a new product or capability and want to start sharing ideas. However, spending a bit of time up front for some homework to research the problem space and to connect with key people almost always yields a high return on investment. There’s a common approach I like to follow when it comes to doing this homework.</p><p>I always start by thinking through the questions that I have about the problem space and writing those down:</p><ul><li>Some questions are typically targeted at gathering a robust understanding of the current state application and/or services that may need to change with a special focus on understanding the key points of integration within a current state application or service context.</li><li>Other questions are based on initial assumptions of what may need to change or be built to solve the problem at hand.</li></ul><p>For each question I use different approaches (or a combination of approaches) to get the answer:</p><ul><li><strong>Review existing documentation and code.</strong> — Almost every design will involve some changes to, or an articulation of, current state components. Often times, there is design documentation you can reference to get up to speed with the current state, but for more detailed changes, or if there is a lack of documentation, spending some time reviewing code is necessary.</li><li><strong>Find out who the key people are, and determine the questions you need to ask them.</strong> — For every IT product, there’s usually an expert that either designed it, wrote the code for it, or at the very least understands how it works. Determining who your key contacts are, and how best to engage with them to get the information you need will be critical to the success of most design efforts.</li><li><strong>Some questions require industry related insight and perspective.</strong> — You can start with some industry research on the internet, but ideally there are some key people within your organization that have already spent a bunch of time researching a specific service or technology and might have even gone as far as a proof of concept. Reach out to these resources to ask about the information that they have and if there’s anything conclusive based on their work.</li></ul><p>This part of the work leans heavily on developing and maturing soft skills that are prone to being overlooked in tech centric organizations. Learning how to adapt to different communication styles and work with people across the organization takes time and practice, but it’s well worth the investment.</p><h3>Step 2 — Outline the Context and Problem Statement</h3><p>One common mistake that I often see inexperienced Engineers or Architects make, is starting off design documentation with a complex design diagram. While a great design diagram is an essential part of almost every design assignment, some context is typically necessary to get the reader to a place where they can make sense of what the diagram is trying to articulate. Leading in with some background context on why you’re working on this design assignment and the problem that intends to be solved helps to guide the reader into your headspace and set up the more detailed design that’s forthcoming. This section should intentionally be kept brief and often times a short bulleted list is the most effective way to introduce all of the key historical context and the general problem statement the reader needs to be aware of.</p><h3>Step 3 — Clarify the Scope and Assumptions</h3><p>I know that most of you are eager to get to a diagram, but trust me, spending a bit of time to clarify what’s in vs. out of scope, and/or key assumptions that are part of your design will make for a more concise and focused design diagram. This section should really be intended to draw the boundaries around the things you plan to cover as part of your design vs. the things that were considered as part of thinking through the design, but don’t necessarily need to be depicted because they either aren’t changing or are existing points of integration that are depicted in detail in other diagrams. By incorporating this section, you are further guiding the reader into your headspace and getting them prepared for what they are going to see next. Similar to the Context section, this section should be kept as brief as possible and a bulleted list usually is the most effective technique.</p><h3>Step 4 — Create a Beautiful Diagram</h3><p>Think about a major purchase you’ve made recently. You likely visited several manufacturer websites while researching that purchase, and made immediate subconscious judgements about the related quality of the products just based on the underlying aesthetics of each website. Similar subconscious judgements are made within the first few seconds of seeing a new design diagram. A diagram that is ugly and challenging to consume might leave you questioning the accuracy of the information it is attempting to depict, or wondering about the overall quality of the product(s). Subconsciously, an unorganized ugly diagram is going to make you think the solution itself is unorganized and ugly regardless of how good it may actually be. In contrast, beautiful diagrams immediately instill confidence in the accuracy and quality of the design.</p><p>Simply put, when it comes to design diagrams, aesthetics matter. Let’s take a moment to review some best practices I’ve collected and used over the years, and then explore how we can apply them to make more beautiful diagrams.</p><h3>Best Practices</h3><ul><li><strong>Borrow from another diagram.</strong> — Find a diagram that either you or someone else created that you think is a similar to the diagram you want to create and utilize the symbols, shapes, colors, and spacing from that diagram.</li><li><strong>Use colors and symbols strategically. </strong>— Use color to group like and contrasting items in the design, to emphasize a key focus area on the diagram, and/or to highlight changes to an existing system. Too much color can create noise or be a distraction, so spend some time to find the right balance of colors that is pleasing to look at and makes the information easy to consume. Also, always have a legend to explain what the different colors and symbols mean.</li><li><strong>Use numbering to articulate flow/steps or for informational call-outs.</strong> — For end-to-end flow diagrams, use numbering to articulate the sequence of end-to-end steps. Numbering may also be helpful in context to other types of diagrams if there’s additional details to expand on in text.</li><li><strong>Use abstraction strategically.</strong> — Use abstraction to make the diagram less messy. (As long as it doesn’t remove important information from the diagram.)</li><li><strong>Connection arrows should be used consistently</strong> — i.e. — All straight arrows or all curved arrows, but not some combination of both. Also, try not to cross arrows if possible.</li><li><strong>Connection arrows should be labeled to clarify ambiguity</strong> — e.g. — An unlabeled connection arrow from an API to a Datastore could mean many different things (create, read, update, delete, or some combination). Use labeling to clarify the intent of the connection as needed.</li><li><strong>Make the alignment and spacing of objects as symmetrical as possible</strong> — Symmetry helps to minimize visual distraction and facilitates more rapid consumption of the information depicted.</li><li><strong>Approach your diagram like an artist</strong> — This practice is more abstract, but is important to take your diagrams to the next level. Everyone has different stylistic preferences, but experiment with options to create your own visual style that makes your diagrams stand out. Over time you may develop a signature style that others may come to admire and replicate.</li></ul><h3>Theoretical Example Case Study</h3><p>I’m going to depict a fairly generic system topology diagram that has UI, API, and Data layers. I created all of these example diagrams using <a href="https://www.drawio.com/">Draw.io</a>. The components on the diagram are also labeled generically to keep the example simple. (i.e. — In real life UIs and APIs would have explicit names/labels.)</p><h4>A Not So Beautiful Diagram</h4><p>For this first example, I’ve created a diagram that goes against several of the best practices above just to illustrate how poor aesthetic choices make a diagram very challenging to consume.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1001/1*y4OA1nTQM1Q5IIkMPijliw.png" /><figcaption>A not so beautiful diagram</figcaption></figure><p>My guess is that you’ve come across diagrams that look something like this several times in your career. It’s both challenging to consume and has informational gaps due to poor design choices. For example, this diagram probably has you asking questions like:</p><ul><li>How does the Data Access API interact with the databases depicted? Is it reading, writing, or some combination of both?</li><li>Is there some meaning behind the different colors used for the APIs and UIs?</li><li>The call to Downstream API 1 has a chain of calls to other APIs that then calls off to the same databases that the Data Access API calls. How are these calls different from the calls the Data Access API is making?</li></ul><h4>A Better Version</h4><p>The version below uses some of the best practices to make the diagram more consumable.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/871/1*y6JN-9kVqPFbsuwpy0csEg.png" /><figcaption>A better diagram</figcaption></figure><p>What are some of the initial instant reactions you have seeing this diagram after reviewing the first example? You likely were able to immediately start consuming the information in the diagram rather than spending the first few moments trying to decipher what the diagram was trying to depict. I’m assuming it also made you feel more confident about the accuracy/quality of the design. A few notes on some of key best practices that were applied:</p><ul><li>Colors, symbols, and connection arrows were applied consistently and a Legend was added.</li><li>The detailed call flow behind Downstream API 1 was abstracted to make the diagram less busy, and a numbered call-out was used to offer a point of departure to those details. <em>Note: This assumes that these details aren’t essential to be depicted in context to this diagram and a more detailed end-to-end diagram for Downstream API 1 is available.</em></li><li>Some of the connection arrows were labeled to reduce ambiguity around the purpose/intent of the connections.</li><li>Alignment changes were made to make the diagram more symmetrical.</li></ul><h4>Making it Stand Out</h4><p>There’s nothing wrong with the updated example above. It follows best practices to optimize readability and consumption, and likely represents a good enough stopping point for sharing the design with or across a few product teams. However, what if your goal is to create a diagram that’s going to be shared at the Enterprise level or to possibly be embedded within a webpage to share outside of your organization. You might want to consider some additional aesthetic enhancements to really make your diagram stand out. The example below highlights a few techniques for stylizing your diagram. Everyone’s personal style is unique, so experiment with what resonates with you and create your own signature style!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/919/1*X7X775gIvY0imbfTI-jHuQ.png" /><figcaption>A more beautiful diagram with style added</figcaption></figure><p>A few notes on the techniques that were used to give this diagram more of a hand drawn style:</p><ul><li>A few different hand written font sets were selected and applied. (Draw.io allows you to apply fonts from the <a href="https://fonts.google.com/">Google Fonts</a> Library.)</li><li>“Sketch” styling was selected for some objects and connectors.</li><li>Connectors were switched from “Sharp” to “Rounded”.</li><li>A background object was added to help make the stylistic choices stand out more.</li></ul><h3>Next Steps &amp; Concluding Thoughts</h3><p>After articulating your design diagram(s) there’s several potential additional sections that could make sense to add to your design documentation based on context. You may have a need to elaborate on design details in context to the diagram, break down high level features/stories, sizing, and ownership of work based on the diagram, or explore alternatives of design decisions that may need to be made. Regardless of the next steps that apply to your specific design assignment, there are a couple of additional key best practices to apply as you start to wrap up the initial draft of your design documentation:</p><ul><li><strong>Review and revise your design documentation</strong> <strong>with peers and experts</strong> in the problem space to ensure that the design is accurate and easy to understand. Design should be approached as a collaborative process rather than something done in isolation.</li><li><strong>Refine the story you want to tell.</strong> — At the end of the day, your design should tell a story. In some cases, it will be the story of why changes need to be made in order to achieve a new business goal or priority, while in other cases it might be a more detailed “how to” manual to help a team deliver some high priority work faster. Revisions to your design documentation should be made with your story and intended audience in mind.</li></ul><p>Following this approach takes time and experience to hone, but you should start to see immediate benefits as soon as your next design assignment if you take the time and effort to apply it. I hope this also gives you a better understanding and appreciation for the important aspects of design that go beyond just creating an amazing design diagram, while also providing you with the best practices and techniques you need to create that diagram.</p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0f328abda9cc" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/level-up-your-design-practices-0f328abda9cc">Level Up Your Design Practices</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Adopting GraphQL Federation at State Farm — Our First Baby Steps]]></title>
            <link>https://engineering.statefarm.com/adopting-graphql-federation-at-state-farm-our-first-baby-steps-ec735c8f5195?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/ec735c8f5195</guid>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[api]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[programming]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Mon, 17 Feb 2025 18:02:28 GMT</pubDate>
            <atom:updated>2025-02-17T18:02:27.733Z</atom:updated>
            <content:encoded><![CDATA[<h3>Adopting Federated GraphQL at State Farm — Our First Baby Steps</h3><p>By <a href="https://www.linkedin.com/in/austin-mehmet">Austin Mehmet</a> and <a href="https://www.linkedin.com/in/brian-vanderbusch-9359254b">Brian Vanderbusch</a></p><h3>Overview</h3><p>State Farm’s digital strategy has evolved over the years, from the early days when we had a simple static home page, to today where we have numerous APIs and multiple separate frontends stitched together to provide a cohesive customer experience. A core part of our web architecture relies on numerous Backend for Frontends (BFFs) that provide central orchestration of REST APIs to ensure our customers get a unified experience across our digital landscape. These API orchestration layers have served us well, but ongoing maintenance has become more cumbersome over the years as the downstream APIs upgrade and evolve over time. Some of our larger orchestration APIs may have been generic enough for multiple various clients to consume, but they never grew to become enterprise-wide solutions. While this architecture is functional, it is far from optimal. Recognizing the need for a more efficient solution, we turned our attention to GraphQL and a federated architecture.</p><h3>The Shift to Federated GraphQL</h3><p>GraphQL was not new to State Farm. It had been developed in isolated pockets within the organization but had never achieved widespread adoption. However, the adoption of federated GraphQL changed our perspective entirely. Why? Because it allows us to unify our numerous web services under a new concept: the supergraph. The supergraph acts as a single point of entry across our wide sprawl of APIs and enables clients to request only the data they need while also not having to navigate a maze of various web service that span paradigms like REST, SOAP, GraphQL, and gRPC. It opens the door to no longer having to maintain massive monolithic backend-for-frontend style APIs, but instead have our downstream domain areas power the client facing applications theoretically removing large chunks from our architecture that could potentially improve performance and reduce cost. We also saw other various benefits such as enabling security at a field level, increasing developer productivity, and greater flexibility to adapting to new business features. But maybe one of the most important aspects was that we saw a large benefit in building and maintaining a single unified schema for describing common business terminology to our clients. It isn’t uncommon for areas to call the same concepts different things. Simple things like addressLine1 being called street1 or just line1 or concepts like a vehicle were being represented as vehicle, car or, in some systems, generically as an insurableRisk. Apollo GraphQL’s approach to GraphQL federation lets us wrangle some of that in and have a more unified schema for modeling our business processes and enables a common language in which we can all talk in.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HHHW9hlB5dSqWBzDS0vtrA.png" /></figure><h3>The Proof of Concept</h3><p>We decided to validate this approach with a proof of concept. Collaborating closely with <a href="https://www.apollographql.com/">Apollo</a> and leveraging their product <a href="https://www.apollographql.com/enterprise">GraphOS</a>, we set out to build a thin slice of what we believed would showcase the full potential of federated GraphQL at State Farm. Our concept focused on modeling a policy retrieval system. This system allowed us to fetch policy details, agent details, claims details, and customer details all through a single supergraph. We ended up building 10 subgraphs that exposed 15 queries, 1 mutation, 63 types and 378 fields. Pages that would require multiple network requests to gather up the data they needed to paint a screen could now all be done with one call against our supergraph. Most of the subgraphs built were facade layers that sat on top of existing REST APIs. This was the simplest and quickest way forward to demonstrating the value of the supergraph to our organization. Much of the complexity we had to wade through was reorganizing existing schema into cleaner consumable parts and enabling the federation of our entities. In terms of languages and frameworks, we mainly stuck to Typescript using <a href="https://www.apollographql.com/docs/apollo-server">Apollo Server</a> but also enjoyed branching off that path and trying Java with <a href="https://netflix.github.io/dgs/">DGS</a> and Go with <a href="https://gqlgen.com/">gqlgen</a>. Many of our internal services are already written in Java with Spring and so DGS has offered a more unique approach of simply embedding a GraphQL interface into existing APIs rather than spinning up new services.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LzAMNGsCMPHmZUTk1ah8xQ.png" /><figcaption>Example query plan from our PoC</figcaption></figure><h3>Challenges We Faced</h3><p>Like any transformative project, our journey with federated GraphQL was not without its challenges:</p><ul><li><strong>Complexity of Integration</strong>: Integrating numerous existing services into a unified supergraph required significant effort. Each service had its own intricacies and dependencies that needed to be carefully managed. In the context of our concept, some constraints of our legacy systems introduced unexpected complexities in tasks like policy retrieval across our various domains.</li><li><strong>Schema Management</strong>: Coordinating schema across various teams and services presented some challenges. Initially, we identified opportunities to improve the consumer-friendliness of our existing REST and SOAP schemas as most were never really intended to be exposed to a UI. We spent a lot of effort working back with these various areas to rethink that schema. We also needed tooling and processes to handle schema evolution and versioning. Apollo’s GraphOS helped solve that problem with their <a href="https://www.apollographql.com/docs/graphos/platform/schema-management/proposals/create">schema proposal changes</a> feature which allowed us to have complete conversations on supergraph schema. We also encountered some difficulties with composition and collaboration within a shared type pool. Specifically, aligning on consistent naming conventions for types used by different teams was an area that required extra coordination.</li><li><strong>Security</strong>: Ensuring the security of our federated graph was paramount. We are still exploring ways to implement stringent access controls and data validation mechanisms to protect sensitive information through the implementation of the <a href="https://www.apollographql.com/docs/graphos/reference/federation/directives#policy">@policy</a>directives via a <a href="https://www.apollographql.com/docs/graphos/routing/customization/coprocessor">coprocessor</a>.</li><li><strong>Scale</strong>: When faced with a large task, such as creating a sustainable supergraph for a large organization, it is important to take extra time to plan and keep the long term goal in mind from the beginning. We knew that our proof of concept would need to be focused on 10 teams spread across our IT operations workforce, and they would become the catalyst for expansion to hundreds of teams in the near future. We started small and very intentional around scope, so that we could build a framework for rapid expansion.</li><li><strong>Forging New Ground:</strong> Our teams span across a vast landscape of products and purposes. In our discovery process, we found many supergraphs at various other organizations that were limited either in their scope or in the size of their graph development workforce. We needed to find a platform and also a vendor that would be willing to grow with us as we test the capabilities and limits of the supergraph concepts.</li></ul><h3>Where We Are Now</h3><p>The success of our proof of concept convinced us of the value of federated GraphQL, and we decided to commit to this new approach. Many of the challenges listed above pointed to the need of some form of a governance team. Additionally, with there being some infrastructure setup and maintenance required to support Apollo federation (running the <a href="https://www.apollographql.com/docs/graphos/routing/self-hosted">Apollo Router</a>), our next step was to build a dedicated platform team around GraphQL at State Farm. This involved assembling a new team and laying down the foundational infrastructure to facilitate easy onboarding for development teams across the organization. This team is responsible for ensuring collaboration across engineering teams, maintaining a clean federated schema, management of any shared infrastructure, and continued advocacy for growing the supergraph. We additionally continue to work closely with Apollo and fully leverage their GraphOS product for our GraphQL federation journey. We have already put one use case into production that enables online customer channel business lines quoting and we have many more on the horizon.</p><p>Currently, our focus is on:</p><ul><li><strong>Gaining Additional Adoption</strong>: We are actively promoting the benefits of federated GraphQL within State Farm and encouraging more teams to adopt this new approach. We have four more use cases we are onboarding onto our production supergraph along with actively attracting more.</li><li><strong>Increase Education and Training: </strong>GraphQL being new to so many areas of State Farm, our platform team is building an education and training plan that leverages self-paced materials and the <a href="https://www.apollographql.com/tutorials/">Apollo Odyssey</a> courses to get team’s up-to-speed.</li><li><strong>Building Out Automation</strong>: To streamline the development process, we are investing in automation for our GraphQL platform. This includes automated shared infrastructure deployments, automated schema validation, CI/CD pipelines for subgraphs, testing tools, and monitoring tools.</li><li><strong>Exploring Apollo Connectors for REST APIs</strong>: We are diving deep into a new feature called <a href="https://www.apollographql.com/docs/graphos/schema-design/connectors">Apollo Connectors</a>. This feature promises to further enhance our ability to integrate existing REST services into our federated graph seamlessly. One of the challenges we face is difficulty getting work prioritized when it involves another team. With Apollo Connectors, we see a large potential in the ability to grow our supergraph without impacting these teams. Streamlining supergraph development and showing a faster return is the goal.</li></ul><h3>The Future Holds Promise</h3><p>Our journey with federated GraphQL is just beginning. As we continue to expand our GraphQL footprint at State Farm, we are excited about the possibilities that lie ahead. In the next six months, we expect to achieve significant growth and learning and plan to share our experiences, insights, and best practices with the community.</p><p>Stay tuned for more updates on our progress as we navigate this exciting transformation. Federated GraphQL has opened up new avenues for innovation at State Farm, and we look forward to seeing where it takes us.</p><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ec735c8f5195" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/adopting-graphql-federation-at-state-farm-our-first-baby-steps-ec735c8f5195">Adopting GraphQL Federation at State Farm — Our First Baby Steps</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[When the Spark Execution Plan Gets Too Big]]></title>
            <link>https://engineering.statefarm.com/when-the-spark-execution-plan-gets-too-big-eb658872d603?source=rss----58687c4d0d68---4</link>
            <guid isPermaLink="false">https://medium.com/p/eb658872d603</guid>
            <category><![CDATA[apache-spark]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[technology]]></category>
            <category><![CDATA[data-engineering]]></category>
            <dc:creator><![CDATA[State Farm Engineering]]></dc:creator>
            <pubDate>Mon, 13 Jan 2025 18:02:05 GMT</pubDate>
            <atom:updated>2025-01-13T18:02:05.308Z</atom:updated>
            <content:encoded><![CDATA[<p>By <a href="https://www.linkedin.com/in/huntermitchell1/">Hunter Mitchell</a></p><h3>Background</h3><p>Apache Spark has been a dominant big data processing tool for around a decade. Its popularity stems from its multi-language support, fault-tolerance, performance, and scalability. The core data structure used by Spark is the RDD (Resilient Distributed Dataset), which represents an immutable collection of objects. One of Spark’s unique features is its execution plan, which gets evaluated lazily to allow optimization once an “action” is called (i.e., a result is returned). Keeping the entire execution plan in memory is what allows Spark to be fault-tolerant; that way, it can rebuild the data if a worker is lost. However, the execution plan can become quite extensive when performing many data transformations, potentially leading to memory bottlenecks.</p><p>I’ve been a Data Engineer for around 3 years and have been working with Spark for most of that time, but I hadn’t run into this problem until I joined State Farm a year ago. This is due to the heavily nested nature of State Farm’s data, which requires us to perform many transformations on it. After these transformations, we noticed the applications would run slower than expected, and the Spark UI would fail to load when we tried to view the execution plan DAG (Directed Acyclic Graph). Then, I learned I could break up the execution plan to alleviate this problem. Creating a breakpoint in the lineage of the data allows us to free up the memory retained by all of its previous transformations. After researching the various ways to do this, I wanted to perform an in-depth analysis of these different options and understand how each one works.</p><p>In this article I’ll be exploring what happens to the Spark execution plan when implementing the following techniques on a transformed dataframe:</p><ol><li>Caching</li><li>Checkpointing</li><li>Local Checkpointing</li><li>Temporarily Writing to Disk</li><li>Rebuilding From the RDD</li></ol><h3>Scenario</h3><p><strong>Disclaimer:</strong> Unless otherwise stated, I will be referring to the <a href="https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html">Pyspark DataFrame API</a>. The <a href="https://spark.apache.org/docs/latest/api/scala/org/apache/spark/sql/Dataset.html">Dataset API</a> and the <a href="https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.RDD.html">RDD API</a> may have slight differences.</p><p>To illustrate this, let’s consider the following simple dataframe:</p><pre>df = spark.range(100)</pre><p>Now let’s perform a self cross-join to force a wide transformation:</p><pre>df_transformed = df.withColumnRenamed(&quot;id&quot;, &quot;id1&quot;).crossJoin(df.withColumnRenamed(&quot;id&quot;, &quot;id2&quot;))<br>df_transformed.show()</pre><p>Now we have a cartesian product of the numbers up to 100:</p><pre>+---+---+<br>|id1|id2|<br>+---+---+<br>|  0|  0|<br>|  0|  1|<br>|  0|  2|<br>|  0|  3|<br>|  0|  4|<br>|  0|  5|<br>|  0|  6|<br>|  0|  7|<br>|  0|  8|<br>|  0|  9|<br>|  0| 10|<br>|  0| 11|<br>|  0| 12|<br>|  0| 13|<br>|  0| 14|<br>|  0| 15|<br>|  0| 16|<br>|  0| 17|<br>|  0| 18|<br>|  0| 19|<br>+---+---+<br>only showing top 20 rows</pre><p>Here’s what the Spark execution plan for this dataframe looks like up to this point:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/645/1*gvrL0YtBPcBpTGYnLuaMFQ.png" /></figure><p><strong>Note:</strong> I’m using the SQL section of the UI to get the execution plan DAG, as it tends to provide a better visual than the Jobs/Stages tabs.</p><p>Although this is just one transformation, you can see how the DAG can grow large if you’re doing many. For each transformation, Spark stores intermediate results, metrics, shuffle information, and execution context metadata. All of which utilize memory.</p><p>Let’s say we want to perform further analysis on this dataframe. If you’re like me, your first thought would be to cache the dataframe.</p><h3>Where Caching Falls Short</h3><p>Caching is the standard approach when planning to reuse a dataframe, and for good reason. It allows quick access to the dataframe by storing the data at the storage level of your choosing. The problem with caching comes from the fact that it retains the history of the execution plan prior to the cache, which can slow down performance if the history is long. As previously mentioned, this is for fault-tolerance purposes. Let’s see an example to prove this:</p><pre>df_cache = df_transformed<br>df_cache.cache()<br>df_cache.count() # force caching<br>df_cache.show()</pre><p>Here’s what the execution plan DAG looks like from the show() action:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/433/1*pDHLjL3BtyXxCKkoGiVyPg.png" /></figure><p>As you can see, the execution plan prior to the cache is still there.</p><p>Despite not breaking up the execution plan, caching should still be the go-to method when needing to reuse a dataframe. I don’t suggest replacing all instances of caching with one of the methods outlined below, though you may consider combining them in certain scenarios.</p><h3>What Other Options Do We Have?</h3><h3>Checkpoint</h3><p>The first option we have is dataframe checkpointing, not to be confused with Spark Streaming checkpointing. Dataframe checkpointing materializes and stores the dataframe’s underlying RDD to a directory. This can be a local temp directory, distributed filesystem like HDFS, or object store like S3. There, it is replicated to ensure fault-tolerance. For this to work, you must specify the directory to store the RDD files. You must also reassign the dataframe from the output of the checkpoint method to properly retrieve the checkpointed dataframe.</p><p>Here’s how this works in practice:</p><pre>df_checkpoint = df_transformed<br>spark.sparkContext.setCheckpointDir(&quot;./checkpoint&quot;) # specify location of RDD files<br>df_checkpoint = df_checkpoint.checkpoint() # returns checkpointed dataframe<br>df_checkpoint.show()</pre><p><strong>Note:</strong> The checkpoint method can also be lazily evaluated, like caching. To do this, set the eager argument to False.</p><p>This stores the serialized RDD files in the .checkpoint/ directory. Now let&#39;s see what the execution plan DAG looks like from the show().</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/396/1*cQ49n-zqKVwB7p99IePBQQ.png" /></figure><p>Voila! It’s now reading from the stored RDD files, and has essentially forgotten about what happened prior to that. The total size on disk of the RDD files from this sample dataframe was 425 KB.</p><p>A few other notes:</p><ul><li>The stored RDD files will not automatically disappear after your SparkSession ends, so it may require extra cleanup depending on your cluster setup and where you store the files.</li><li>When checkpointing an RDD directly (not the dataframe), it’s recommended to cache the RDD before checkpointing. This is because there are actually 2 jobs happening on the RDD, requiring recomputation. For more info on RDD checkpointing, check out <a href="https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.RDD.checkpoint.html">the docs</a>.</li></ul><h3>LocalCheckpoint</h3><p>Similar to checkpointing, local checkpointing also materializes the dataframe’s RDD. However, this gets stored in memory rather than on disk, making it perform faster. This makes it work the same as caching, but without keeping the prior execution plan. The storage level that gets used is MEMORY_AND_DISK_DESER, just like the cache default, though it doesn&#39;t appear to be configurable. The problem with local checkpointing is that it&#39;s not fault-tolerant, meaning that if you lose an executor, the RDD will not be able to be reconstructed because it doesn&#39;t have the transformations that created it. This is especially troublesome if:</p><ul><li>you’re using dynamic allocation within your cluster, which can drop executors</li><li>you’re using auto-scaling with a service like AWS Glue/EMR, which can also drops workers</li><li>you’re using AWS Spot Instances for your workers, which can be interrupted or shut down at any time</li></ul><p>I recommend avoiding dataframe local checkpointing except for one-off cases, like adhoc analysis notebooks where you can afford things to fail.</p><p>Here’s how this looks in code:</p><pre>df_local_checkpoint = df_transformed<br>df_local_checkpoint = df_local_checkpoint.localCheckpoint() # need to reassign the df just like checkpoint<br>df_local_checkpoint.show()</pre><p>Here’s what the execution plan DAG looks like:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/373/1*wVN2ZPOunCggZQ-kfIWd3Q.png" /></figure><p>It looks just like the checkpointing DAG, which is to be expected. One difference is that the stored RDD files are bigger than they were when checkpointing — for this example they take up 840 KB of memory. This is likely due to them not being serialized, which provides quicker access to them.</p><h3>Temporary Write to Disk</h3><p>The third option is explicitly writing the dataframe to disk, and reading it back. This allows flexibility because you can write the dataframe anywhere, even your preferred database. It also allows storage-optimized file types like parquet which occupy less space and may incur less external storage costs as well. Here’s how you can implement this:</p><pre>df_temp_write = df_transformed<br>df_temp_write.write.mode(&quot;overwrite&quot;).parquet(&quot;./temp_write&quot;)<br>df_temp_write = spark.read.parquet(&quot;./temp_write&quot;)<br>df_temp_write.show()</pre><p>Which provides the following DAG:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/393/1*oIlYSFukpWUN8nJQsvfETQ.png" /></figure><p>This is slightly different from the other DAGs because it needs to translate the parquet files back to an RDD. Therefore, if you plan on re-using this dataframe heavily, you may want to cache it after reading it from the temporary location.</p><p>This option, like checkpointing, will likely require manual cleanup of the files. However, there is less data to cleanup because of the optimized storage. For this example, the parquet files on disk amounted to just 18 KB! It’s also worth mentioning that the order of the dataframe can be different when reading it back, so you may need to sort it before writing the files.</p><h3>Rebuilding the Dataframe from the RDD</h3><p>I saw a few examples online of people saying that recreating the dataframe from the RDD and the schema will effectively break up the execution plan just like the other options. Let’s see if this works:</p><pre>df_rebuild = df_transformed<br>df_rebuild = spark.createDataFrame(df_rebuild.rdd, df_rebuild.schema)<br>df_rebuild.show()</pre><p>When we look at the DAG in the SQL section of the Spark UI, it looks like it works:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/380/1*GiMVRrMjLQhy8Zc0Rrh1gA.png" /></figure><p>However, if we look at the DAG from the Job section of the Spark UI, we’ll see a different story:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/250/1*hiX1teOBtYNiVNjw8xXH-w.png" /></figure><p>This appears to show the original df_transformed job&#39;s execution plan with a few extra steps of converting it to an RDD and back to a dataframe. I believe the SQL section is misleading because the transformation is actually happening with the RDDs, which is a lower level than the SQL interface. Since this doesn&#39;t actually break up the execution plan, this isn&#39;t a viable option.</p><h3>Comparison</h3><p>Here’s a simple table to show the main differences in the techniques we’ve covered:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ioqKnhsQSx5pZkx02WunOw.png" /></figure><p>In an attempt to get a direct comparison of how well these methods perform, I designed an experiment. The experiment creates a dataframe, runs a bunch of transformations on it to build up the execution plan, then iteratively performs a number of actions which reuse the dataframe. Before the last step, I implemented each strategy outlined above to see how it impacted the resulting processing time.</p><p>One hurdle I encountered when designing this was Spark implicitly storing some of the results, which would make the first test always run slower than the following tests. To get past this, I introduced randomness to the actions so that each test would calculate slightly different aggregates.</p><p>The 3 major variables which I expect to impact the performance are:</p><ol><li>Size of the dataframe</li><li>Size of the execution plan</li><li>Number of times we reuse the dataframe</li></ol><p>Therefore, I ran this experiment with a few different values for each of these. Here are the results:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*VLQpf6upRKb88tbuQ8OzuA.png" /></figure><p>The first column # Dataframe Rows simply represents how big the dataframe is. The second column # Dataframe Transformations represents how big the Spark execution plan is. The third column # Dataframe Actions is how many times I reuse the dataframe after implementing each strategy. This is all local, so I ran into memory errors when trying to go past these numbers. The Baseline Time column is how long the code took when I didn&#39;t implement any strategy.</p><p>A few interesting things to note with these results:</p><ul><li>Caching performed relatively poorly in each test, and even worse than the baseline in two of them</li><li>Local checkpointing, despite its risks, outperformed the others in all tests</li><li>Checkpointing performed slightly better than temp writes in each test, however, temp writes seemed to be getting better when we increased our variables</li><li>Rebuilding the dataframe performed okay when our dataframe was small, but severely degraded as it grew</li></ul><p>Of course, these will not always hold to be true. Another potential factor which I didn’t explore is the complexity of the dataframe actions we’re performing after the strategy. In my experiment, we run a simple filter and sum aggregation on a single column, but doing something more complex might change which strategy performs the best.</p><p>The full code I used for my experiment can be found <a href="https://github.com/huntermitchell123/spark_execution_plan_article/blob/master/spark_methods_experiment.ipynb">here</a>.</p><h3>Which to Use?</h3><p>As with most Spark applications, there’s not one solution that will work the best for everyone. Therefore, I recommend experimenting with the above methods, and determining which provides the most uplift for your situation. With that being said, there are a few conclusions we can accurately draw:</p><ol><li>Caching should still be the default technique when you need to reuse a daframe which doesn’t have a big execution plan.</li><li>Local Checkpointing is generally the most efficient strategy to break up your execution plans. However, there are major drawbacks when it comes to fault-tolerance.</li><li>The next best option is typically either checkpointing or temporarily writing to disk. These tend to perform similarly.</li><li>Rebuilding the dataframe from the RDD does not properly break up the execution plan and should be avoided.</li></ol><p>For our application, we found that temp writes with caching afterwards was the best option.</p><h3>Departing Thoughts</h3><p>Breaking up the execution plan can help speed up any Spark applications where you are reusing a transformed dataframe. This approach works by removing the unnecessary information stored from all previous transformations, which frees up memory. Therefore, it tends to be most effective when there have been many transformations on the dataframe or when you’re reusing it many times. These strategies often perform better than simply caching, which keeps the metadata from previous transformations. For us, breaking up the execution plan provided more than 40% reduction in cost over caching. We found this strategy to be useful in our data processing pipelines, but I have also seen mentions of it being used in MLlib and GraphX apps. Hopefully it can benefit your Spark apps as well!</p><p>Please don’t hesitate to reach out with any questions or thoughts.</p><p>All of the code I used is available on <a href="https://github.com/huntermitchell123/spark_execution_plan_article">my github</a>.</p><p>If you’re interested in researching this yourself, I found the following resources especially helpful:</p><ul><li><a href="https://www.getorchestra.io/guides/spark-concepts-pyspark-sql-dataframe-checkpoint-getting-started">https://www.getorchestra.io/guides/spark-concepts-pyspark-sql-dataframe-checkpoint-getting-started</a></li><li><a href="https://github.com/JerryLead/SparkInternals/blob/master/markdown/english/6-CacheAndCheckpoint.md">https://github.com/JerryLead/SparkInternals/blob/master/markdown/english/6-CacheAndCheckpoint.md</a></li><li><a href="https://jaceklaskowski.gitbooks.io/mastering-spark-sql/content/spark-sql-checkpointing.html">https://jaceklaskowski.gitbooks.io/mastering-spark-sql/content/spark-sql-checkpointing.html</a></li></ul><p><strong>To learn more about technology careers at State Farm, or to join our team visit, </strong><a href="https://www.statefarm.com/careers"><strong>https://www.statefarm.com/careers</strong></a><strong>.</strong></p><p><strong>Information contained in this article may not be representative of actual use cases. The views expressed in the article are personal views of the author and are not necessarily those of State Farm Mutual Automobile Insurance Company, its subsidiaries and affiliates (collectively “State Farm”). Nothing in the article should be construed as an endorsement by State Farm of any non-State Farm product or service.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=eb658872d603" width="1" height="1" alt=""><hr><p><a href="https://engineering.statefarm.com/when-the-spark-execution-plan-gets-too-big-eb658872d603">When the Spark Execution Plan Gets Too Big</a> was originally published in <a href="https://engineering.statefarm.com">State Farm Engineering Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>