Crafting the Web: Tips, Tools, and Trends for Developers Advertise with Us|Sign Up to the Newsletter @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } WebDevPro #144 Is Your Service Architecture Working For You or Against You? Hi , The debate around microservices never really goes away. You will find equal conviction on both sides: engineers who swear by them and those who have spent years untangling the mess they can become. The truth, as it usually is in software architecture, sits somewhere in the uncomfortable middle, and understanding that middle space is what separates developers who pick architectures thoughtfully from those who follow the current consensus without questioning it. Microservices promised a clean world. Small, isolated services, each doing one thing, each deployable on its own. In theory, a developer can work on a single service, test it in isolation, and ship it without needing to understand the rest of the system. For teams burned by monolithic codebases that had grown into unmaintainable sprawl, the appeal was real. But the promise comes with costs that compound as a system grows, and most architecture discussions gloss over them until a team is already deep in the weeds. Before we dig deeper into this, here's a TL;DR you'll need: 🎨 Node-RED 5.0: the biggest editor overhaul in the project's history 🖌️ A Jane Street designer now prototypes in Claude Code more than Figma 📝 How building an HTML-first site doubled a company's users overnight 📅 Node.js is finally fixing its confusing release schedule 🪟 How Aave built a Liquid Glass component library for the web 👾 Can Chrome's built-in AI play Zork? Not really The Docker Build Loop Nobody Warned You About Consider what happens when you begin with a straightforward to-do service and an auth service. Two services, manageable. But product requirements evolve, as they always do. You need email notifications when users are added to groups. You need rate limiting on registration to prevent email flooding. You need organizational structures with departments, permissions, and roles. Suddenly you have five services, each with its own database, each with dependencies on at least two others. In theory, microservices are meant to be independent. In practice, real production systems rarely achieve this. Services end up needing data from each other constantly, and each new feature decision pulls at those dependencies further. The dream of true service isolation runs into the reality of shared business logic. The first place this becomes painful is local development. When you are building a feature that touches the org service, the auth service, and the to-do service simultaneously, you are no longer working on one small service. You are building across three repos, running multiple Docker containers, waiting on separate builds, and debugging the network glue between them every time something breaks. For a small team moving fast, this overhead does not scale the way the architecture diagram suggested it would. There is a real pattern here: teams start with microservices because the architecture looks clean on a whiteboard, then spend a disproportionate amount of their engineering time not building features, but managing the plumbing between services. When Your Services Have No Idea What the Others Are Doing There is a subtler problem that compounds the above, and it is harder to see until a system reaches a certain size. In a microservices architecture, no single service has full visibility into what is happening across the system. Each service operates in its own process, in its own memory space, talking to others only through network calls. This creates a condition that is easy to underestimate: services end up duplicating checks on each other's work because no service can fully trust what any other service has already done. A classic example is auth verification. A developer working on the org service cannot know with certainty that the upstream to-do service has already verified the user's role. So they add their own auth check. Then a project management service is built later, and it does the same. Eventually, a single user action triggers multiple redundant calls to the same auth service, a phenomenon that plays out at enormous scale in large systems. DoorDash documented a version of this internally, where a single order placement triggered roughly 300 calls to the account service, precisely because all downstream services independently wanted to verify account state. Beyond the redundant calls, there is the problem of circular dependencies. In a modular codebase, the compiler catches a circular dependency at build time and refuses to proceed. In a microservices architecture, those circular dependencies manifest at runtime as network calls, completely invisible at compile time. By the time you discover the problem, you may be debugging a cascading failure in production What Nanoservices Actually Get Right The nanoservice model, applied in production on Kubernetes clusters, attempts to preserve the isolation and modularity benefits of microservices while cutting out the operational costs that make them painful at small-to-medium scale. The core insight is architectural rather than infrastructure-level. Instead of splitting services at the process boundary, with each service in its own Docker container communicating exclusively over the network, nanoservices split at the code module boundary, organized into layers: a networking layer, a core logic layer, and a data access layer. Each layer is independently testable and independently swappable, but they are compiled into a single binary. This matters in a few concrete ways. Builds are faster because you are not waiting for multiple Docker builds and inter-service network configuration to line up. Circular dependencies become compiler errors rather than runtime surprises. When one core function needs data from another service, that data can be passed directly in memory rather than serialized, sent over a network, deserialized, and returned. The performance difference is not marginal. Critically, this does not close off the option of deploying as microservices later. If a particular service grows large enough to warrant independent deployment, maybe it has distinct scaling requirements or a distinct team working on it, it can be extracted. The code is already structured to make that transition clean. You are not locked in either direction. Asking a Better Question Than Monolith vs. Microservices The monolith-vs-microservices question is usually the wrong question to lead with. The more useful question is: what granularity of modularization serves the team and product at this stage of the system's life? A small team building quickly should probably start with something closer to a nanoservice structure: clean module separation, well-defined layers, everything compiled together. This gives the developer experience of working on isolated, understandable pieces without the operational weight of managing multiple networked services. If the system grows and a genuine case emerges for pulling out a service, the modular structure makes that extraction tractable. The teams that regret their architecture decisions are usually the ones that adopted microservices as an upfront commitment rather than as an option they might grow into. They took on the full operational complexity of the architecture before the system warranted it. There is a related cost worth naming: microservices architectures are expensive to observe. Without investment in centralized logging, distributed tracing, and monitoring across all services, debugging becomes genuinely difficult. A network call fails somewhere in a chain of five services, and identifying where and why becomes a non-trivial investigation. Observability infrastructure is often an afterthought until the pain of not having it becomes too great to ignore. Why Rust Makes This Argument Stronger Most architecture discussions about microservices are language-agnostic, but they should not be entirely. Rust brings something specific to this conversation: the compiler as a collaborator rather than just a validator. When services are compiled together into a single binary, Rust's type system and borrow checker enforce contracts between modules at build time. A bad interface, a circular dependency, an incorrect assumption about ownership, these surface as compile errors before they become production incidents. This is a qualitatively different experience from working across service boundaries in dynamically typed languages, where the contract between services is enforced only at runtime, usually through documentation, convention, and hope. Rust makes the case for compiling services together stronger than it would be in a language with weaker static guarantees. There is also the performance dimension. The latency cost of an in-process function call versus a network call is not trivial, especially as request complexity grows and the number of inter-service calls per request increases. Compiling services into a single binary eliminates that latency for cross-module communication, while Rust's zero-cost abstractions ensure you are not paying hidden runtime costs elsewhere to achieve it. Production-Readiness Is a Design Decision, Not a Final Step One pattern worth internalizing is that production-readiness is not a state you arrive at after building; it is a set of decisions baked into how you structure code from the beginning. This means thinking about error handling across module boundaries early, building your logging and observability layer before you need to debug a production issue, and testing not just individual units but the interactions between layers. The architecture you choose shapes how tractable all of this is. Clean module separation with explicit data access layers makes unit testing far more manageable because you can mock dependencies at the boundary. End-to-end atomic tests become more reliable when you are not introducing network uncertainty into every interaction. Deployment pipelines simplify when you are shipping one binary rather than coordinating releases across a constellation of services. None of this is an argument against microservices in general. At sufficient scale, with sufficient team size and operational maturity, microservices solve real coordination problems. But for most web developers working on most systems at most stages of a product's life, the more honest architecture discussion starts with modular code that compiles together, and evaluates the case for network boundaries only when a concrete, specific need for them emerges. Speaking of building production-ready applications in Rust, we are running a live workshop with Francesco Ciulla where you will work through exactly these kinds of real-world engineering decisions. Using Claude and Codex as AI-native workflow tools, you will scaffold, debug, refactor, test, and ship a practical Rust application live. The workshop covers backend setup, AI integrations, retrieval and chat systems, and production-ready patterns; the kind of hands-on work that makes these architectural concepts concrete. Build Production-Ready AI Applications with Rust, Claude, and Codex. Claim Your Discounted Seat This Week in the News 🚨 Anthropic pulls its newest AI model after the US government flags security concerns: Days after launch, Anthropic was ordered to disable Claude Fable 5 and Mythos 5 for all users over a possible jailbreak, letting it exploit cyber defences. The UK's AI Security Institute reportedly found the model could breach systems 73% of the time in testing. This one's worth watching closely, it's tangled up with an ongoing Anthropic vs Trump administration lawsuit and raises real questions about how "too powerful to release" models get governed going forward. 🎨 Node-RED 5.0: the biggest editor overhaul in the project's history: A built-in dark theme, split sidebars, pausable debug output, and Function nodes that can now call Link nodes and wait on a response. If you've been running Node-RED on autopilot for years, this release is the nudge to finally poke around the editor again. Just know it now demands Node.js 22.9+ minimum. 🪟 How Aave built a Liquid Glass component library for the web: Timed neatly with WWDC, Lochie Axon walks through recreating Apple's Liquid Glass aesthetic as a cross-browser component library. Worth a look if you've been itching to bring that frosted, refractive UI trend into your own web projects without waiting on native support. 📅 Node.js is finally fixing its confusing release schedule: Starting October 2026, Node ditches the odd/even LTS dance for one major release a year, with version numbers matching the calendar year (27 in 2027, and so on). Every release becomes LTS, plus a new Alpha channel gives library maintainers a six month head start on breaking changes. If you only run LTS, this barely changes your life, which is exactly the point. Beyond the Headlines 📝 How building an HTML-first site doubled a company's users overnight: A genuinely great read on rebuilding a regulated utility's broken React form using Astro, server redirects, and a tiny validation web component, no client-side state management in sight. The user numbers doubled the moment it shipped, and the takeaway about who gets bounced by heavy JS is one every product team should sit with. 🧩 How TypeScript actually infers type variables: If generic inference has ever surprised you in a way that felt like TypeScript was just messing with you, this breaks down the candidate collection and resolution process in painful detail. Dense, but it's the kind of dense that actually resolves years of "why does TS do that" confusion. 👾 Can Chrome's built-in AI play Zork? Not really: Raymond Camden hooks Chrome's on-device Prompt API up to a JS port of Zork 1 and watches it wander in circles examining the same pile of leaves. A fun, low-stakes experiment that's also a decent gut check on how far local, lightweight models actually get with multi-step reasoning. 🖌️ A Jane Street designer now prototypes in Claude Code more than Figma: Edwin Morris describes skipping mockups entirely and shipping working prototypes, including 2000+ line diffs, directly with Claude. The honest bit about what this does to code review and the "disposable prototype" framing is the part worth chewing on, not just the productivity win. 🧊 Draco.js: a pure JS mesh loader for three.js, by mrdoob himself: A from-scratch Draco decoder with no WASM dependency, plus a live demo to poke at. If your three.js pipeline has been fighting with Draco's official loader, this is worth bookmarking. The Developer Toolbox 📄 Extend UI: open source React components for document apps PDF, DOCX, XLSX, and CSV viewers with bounding box citations, file upload, and e-signing, all ready to drop into agents or internal tools. If you're tired of rebuilding document viewers from scratch every time a client wants "AI that reads my files," this saves a real chunk of work. 📧 free-email-domains: a maintained list of every free email provider Kiko has compiled and actively maintains a list of free email domains, perfect for screening signups and flagging spammy or throwaway addresses. Small utility, big quiet usefulness. That’s all for this week. Have any ideas you want to see in the next article? Hit Reply! Cheers! Editor-in-chief, Kinnari Chohan 👋 Advertise with us Interested in sponsoring this newsletter and reaching a highly engaged audience of tech professionals? Simply reply to this email, and our team will get in touch with the next steps. 📢 Important: WebDevPro is Moving to Substack WebDevPro will soon move to Substack. Future issues will come from packtwebdevpro@substack.com so please add it to your contacts or whitelist it to keep receiving the newsletter without interruption. SUBSCRIBE FOR MORE AND SHARE IT WITH A FRIEND! *{box-sizing:border-box}body{margin:0;padding:0}a[x-apple-data-detectors]{color:inherit!important;text-decoration:inherit!important}#MessageViewBody a{color:inherit;text-decoration:none}p{line-height:inherit}.desktop_hide,.desktop_hide table{mso-hide:all;display:none;max-height:0;overflow:hidden}.image_block img+div{display:none}sub,sup{font-size:75%;line-height:0}#converted-body .list_block ol,#converted-body .list_block ul,.body [class~=x_list_block] ol,.body [class~=x_list_block] ul,u+.body .list_block ol,u+.body .list_block ul{padding-left:20px} @media (max-width: 100%;display:block}.mobile_hide{min-height:0;max-height:0;max-width: 100%;display:none;overflow:hidden;font-size:0}.desktop_hide,.desktop_hide table{display:table!important;max-height:none!important}.social_block .social-table{display:inline-block!important}} @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} } @media only screen and (max-width: 100%;} #pad-desktop {display: none !important;} }
Read more