So as well as rolling out features, it was used to configure feature permissions and usage limits like API rate limits, pricing plans and billing logic, in-app copy, CMS content, internationalization and localization, forms, page layouts and funnels, all of our AI models, prompts, temperatures, weights, chains, prediction thresholds, rules to automate approvals and detect spam and fraud, redirect maps, timeouts, magic numbers, third-party integration settings, and automated back-end jobs, and a large long tail of other kinds of configuration. But across all of these different configuration use cases, we got the same high-level benefits. So number one, we got instant updates decoupled from code deployments and the release cycle. Number two, we decoupled dependencies in our organization. So we empowered others to self-serve and make changes by themselves without that slow back-and-forth process with us. And then it also meant that we could focus on shipping code to production without being blocked by them. And then number three, once we had all of this rich configuration, it became easy to optimize it with A-B testing and automated AI loops. And then number four, we simplified our system by extracting all this configuration logic, like user-specific code, out of different code bases and into a single source of truth. And then we also separated the concerns of code and configuration so we could focus on the high-level logic in our code and then worry about configuration details later or give someone else control over that. And then number five, we got more flexibility so we could adapt to changing requirements without needing to make code changes.
So app configuration is great, but how do we go about building a flexible app configuration system that can scale to all of those complex use cases that we went through? Well, first we need a flexible schema language and a type system to define the inputs going into our system, like the current environment or user, and the outputs coming out of our system, like our feature flags or the pricing plans that we offer. Second, we need a flexible configuration language to define the logic that maps the inputs to the outputs, and we should be able to embed A-B tests and AI loops into our logic too. And then we need a UI for non-technical people to edit that logic visually. And then third, we need an analytic system to log events and then see the results of our A-B tests and then collect training data for the AI loops. So when we combine those three ingredients, a flexible schema language, flexible configuration language, an analytic system, we can scale to all the different use cases that we went through earlier. So this is really powerful, but it's also a bit scary as we're moving business logic out of our code base and into our configuration system. So if the system fails or we make an incorrect change, it could cause major issues. And we also don't want to impact the performance or efficiency of our app by querying an external configuration system. So how can we make the configuration as safe, reliable, performant, and efficient as our own code?
Well, first, we can design the SDK to locally evaluate our configuration logic. So it fetches all the configuration logic from the server upfront in one initial network request. And then after that, there's no dependency on the server to evaluate configuration. So it's super reliable, performant, and efficient. But we still have that one initial network request that could fail. So to make that more robust, we can include a snapshot of our configuration logic in our app bundle so the SDK can use that as a fallback in case it can't initialize from the server. And we can also set up a webhook to trigger a new build and deploy of our app whenever the configuration logic changes, so we can keep that snapshot up to date. And then if we do that, we don't even need to initialize from the server at all, and we can use the SDK in a local only offline mode. But then we do lose the benefit of being able to update the configuration instantly from the server. But if we combine those approaches where we have the up to date build time snapshot and the instant server initialization, then we get the best of both worlds. The next thing we can do is that when configuration is evaluated, we can pass hard coded fallback values for the case when the SDK hasn't initialized and there's no snapshot. We can also ensure that configuration is evaluated with full end-to-end type safety to avoid typos, catch errors at compile time rather than runtime, and enable a modern developer experience with IDE integration for code completion, find all references and js.comments. And then finally we can version control all of our configuration in a git-style history so we can see exactly what changed and when with clean diffs of our changes and branches the test and preview changes before merging them into the main branch for the pull request. We can also layer on team roles and permissions to require changes are first reviewed on a pull request to safely onboard non-technical team members. So to recap with local evaluation, build time snapshots, type safety and git-style version control we can get a similar level of safety, reliability, performance and efficiency as our own code. There is no tool or platform out there like this so we built Hypertune and there are some links to check us out. And thank you for listening today. I hope you enjoyed the talk.
Comments