First, we're going to look at a financial dashboard over here, and what I'm going to do is I'm going to simulate some latency in an API response where we're making a request on the server to be able to fetch data, to be able to have it in the initial HTML and send it to the client. What happens in this case is because we're doing this work in, let's say, get server-side props in Next.js, everything is blocking. You're not sending anything to the client until the requests are resolved, and let's say those requests take a super long time, your fastest rendering time can only be as fast as your slowest request. And so at some point, you may want to implement a strategy where you bail out from something that's taking too long, and browsers have much longer time-outs for such things, maybe even up to 30 seconds, so you may want to have your own custom ones for this. So this is an example of how that may have been implemented for that financial dashboard. You have a get server-side props, we're doing a prefetch over here with some sort of request for these transactions, we're dehydrating that, we're passing in the props, and what we can add is a layer on top is just a function to create that prefetch where we maybe pass a time-out of some sort that uses a promise.race under the hood, and we pass a 500 millisecond time-out or something else, maybe even a two-second time-out, and then depending on if the time-out triggers first or the request results first, then we can bail from that, and then because there will be no cache ready on the client side when the hydration takes place, Tanstack query will match up the query keys and try to essentially make sure that we have that data being fetched on the client side, instead of trying to prepare it on the server side. In this case, we're creating a great user experience in the best case scenario, but in suboptimal conditions, we're still optimizing for user experience and bailing out from those conditions. Another way to go about this, if you've got a later version of Next.js or you're working with a much more modern stack, you can implement streaming which allows you to start that same request on the server and carry it through to the client, and everything can resolve in its own manner, and you're not blocking anything, essentially. So Next.js 16.3 is worth trying out for this kind of stuff with partial pre-rendering, cool techniques you can do to add resilience and make sure you're doing blocking work with suspense.
The next one I want to ask is anyone ever heard of a bulkhead pattern before? Hands up. Okay, a couple of people. I can guarantee you at least 50 per cent if not 90 per cent of the people here have used a bulkhead pattern before. So the bulkhead pattern isolates a part of a system into a separate pool, isolates one part of it that fails, or gets overloaded, or something happens that does not impact the rest of the system. Error boundaries, right? Anyone here heard of error boundaries? That's a lot more hands. So, bulkhead patterns, something that's more a broader software engineering term, again, something we use every single day, but it's really good to be able to associate these patterns, and so who here is just throwing a global error boundary on the application and they're happy with that? I see that very, very, very commonly. So we're not surprised when we see a scenario like this where we have multiple different pieces of UI and then, boom, application error, or I'm going to type something in here and then all of a sudden I type the wrong thing, I'm going to crash again, right? Why is one part of the UI impacting an unrelated part of the UI? And even further from that, if I open up Sentry, and I was just capturing Sentry, whatever the raw stuff that I was receiving from those errors being thrown, I see error, type error, error. Anyone know what's going on? I would ask the rubber ducky, but it's a little tired right now. Okay. So, I think that's also a little bit unacceptable to not be able to diagnose fast enough, so I really want you to think about trying to build quadrants when it comes to error boundaries, trying to isolate as close to where you think relevant functionality should be scoped, and so, looking at this quadrant error boundary, we're doing a couple things over here. The first thing is that we're actually going to be defining something called an error tag, and we'll see how that comes in handy in a second. Every time you define an error boundary, you pass an error tag and using the type system to ensure that it ends in error, but it doesn't matter if it just ends in error, you can essentially use the string templating to be able to put boundary error at the end, or you're taking advantage of the type system to enforce some level of tagging that you can propagate to your error reporting tool like Sentry to make it much easier to debug when a scoped piece of your application goes wrong. In that component, it then passed that to this report error function, which takes this error tag, and then, in that, we're essentially just creating a new error from that, passing the original one as a cause, and then we report that to Sentry. What does it look like when we start to isolate things a little bit closer to where they're going wrong? Not only do we now have unique UIs that exist just for where things are going wrong, like, if my nav bar is broken and for some reason errors out, why should my widgets also suffer as a result? You can then have user experiences that are adapted to the failures that are happening. But beyond that, because you're passing error tags, you now receive things like timer countdown error, input render error, you can isolate exactly where in the codebase the issue happened, and that means because you're using also the type system to guarantee that they're all unique identifiers, what you can do is you can then just copy and paste whatever the title of that error was into your IDE, search by it, and you know exactly where that error happened rather than having to run through a stack trace. Because a lot of us get super overwhelmed when we receive a lot of Slack messages and alerts from how many errors happen in Sentry, how many errors happen that we get via email. Sometimes we just mute and ignore those channels, and what's great is we'll receive an email like this with a button no worky error, and I know exactly what went wrong in my application, and I can triage effectively rather than getting overwhelmed. But all in all, how do we measure success with this? And that's where I want to talk a little bit about dora metrics. Anyone here heard of dora metrics before? A little bit about DevOps research and assessment metrics, and they're around four core things. Deployment frequency, how often you deploy code, lead time for changes, time from code commit to running a production, mean time to recovery, which is how quickly a service is restored after an incident, and change failure rate, the percentage of changes that cause incidents. Now why do I love these metrics so much? Because I think we've sometimes over-glorified unit testing coverage sometimes, or all these other metrics that are much easier to game, and I'm sure these dora metrics can be gamed too, but a lot of us focus a lot about how many unit tests we have, or how many our AI are generating, integration tests, end-to-end tests, what lint rules, how our perfect Cloud MD files are, or whatever else it is.
Comments