<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Justin</title>
    <description>The latest articles on DEV Community by Justin (@jusjustin).</description>
    <link>https://dev.to/jusjustin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3822349%2Fa98f1d73-2613-4dff-9eeb-471ef29d6ed8.png</url>
      <title>DEV Community: Justin</title>
      <link>https://dev.to/jusjustin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jusjustin"/>
    <language>en</language>
    <item>
      <title>Legacy Modernization Without a Full Rewrite: A Practical Guide</title>
      <dc:creator>Justin</dc:creator>
      <pubDate>Tue, 29 Sep 2026 13:51:01 +0000</pubDate>
      <link>https://dev.to/jusjustin/legacy-modernization-without-a-full-rewrite-a-practical-guide-n4l</link>
      <guid>https://dev.to/jusjustin/legacy-modernization-without-a-full-rewrite-a-practical-guide-n4l</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;TL;DR&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Legacy modernization is the process of updating systems that have become hard to change or scale, without losing the business logic built into them over years. Most modernization projects don't stall because teams pick the wrong pattern. They stall because teams underestimate the integration cost, the work of bridging what's been extracted back to what remains. This guide covers the Strangler Fig pattern, how to pick a first extraction candidate that won't stall, three supporting patterns for different extraction problems, and where the real cost of modernization actually hides.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Getting Started: What Legacy Modernization Actually Means&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Legacy modernization is the process of updating or replacing software systems that have become difficult to change, scale, or maintain, without losing the business logic and institutional knowledge built into them. Most legacy systems weren't built badly. They were built for the constraints of their time, a smaller team, a different scale, a deployment model that made sense a decade ago. The problem isn't the original decisions. It's what compounds on top of them: new features bolted on, abstractions that leak, a codebase that gets harder to reason about with every change.&lt;/p&gt;

&lt;p&gt;Modernization covers a spectrum of approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Replatforming&lt;/strong&gt;: moving the system to new infrastructure without changing the code. Lowest risk, lowest reward.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring&lt;/strong&gt;: restructuring internal code without changing external behavior. Improves maintainability but doesn't touch architectural constraints.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-architecting&lt;/strong&gt;: changing the system's structure, typically monolith to services, while preserving business logic. Higher risk, higher reward.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replacing&lt;/strong&gt;: rewriting from scratch. Justified only when the existing system is fundamentally unsalvageable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guide covers the space between re-architecting and replacing, moving toward a more flexible architecture while keeping the system live and delivering value throughout.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Modernization Projects Actually Stall&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Legacy systems are hard to modernize not because the code is bad, but because the coupling is invisible until you move something. A module that looks self-contained can have tendrils reaching into shared databases, shared utility libraries, and runtime contracts nobody documented.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk1lfwmcufedpsep6cnzm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk1lfwmcufedpsep6cnzm.png" alt="Devto" width="512" height="244"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Three failure patterns repeat across modernization projects regardless of team size or stack. The first is the big-bang rewrite: a team estimates 12 months, delivers in 24, and ships a system that no longer matches what users need because requirements shifted mid-rewrite. A Rails monolith being rewritten in Go is a common shape for this. By the time the Go version ships, the original Rails app has absorbed 40 new feature commits the rewrite never accounted for.&lt;/p&gt;

&lt;p&gt;The second is the partial migration that freezes. A team extracts auth and the product catalog cleanly, then hits order management, a module that writes directly to 11 tables shared with inventory, billing, and fulfillment. No clean extraction path exists without touching all four services at once. The migration stalls, and the organization ends up running two systems indefinitely, carrying the complexity of both and the benefit of neither.&lt;/p&gt;

&lt;p&gt;The third is integration cost exceeding extraction value, and it's the most common and least discussed of the three. A Python data service extracted from a Java monolith is clean on its own. Bridging it back to the monolith requires a Java HTTP client, a DTO mapping layer, a versioning strategy, and error handling the original in-process call never needed. The extracted service takes two weeks to build. The bridge takes three. Multiply that across five more extractions in three languages, and the integration work consumes every hour the extraction was supposed to save.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Strangler Fig Pattern Explained in Plain Terms&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The Strangler Fig pattern is the foundational approach to incremental modernization. Named after a vine that grows around a host tree and gradually replaces it, the pattern extracts functionality from a legacy system piece by piece while keeping the system live throughout.&lt;/p&gt;

&lt;p&gt;The core mechanism is a routing layer, an API gateway, a reverse proxy, or an application facade, placed in front of the legacy system. As new services get built, the routing layer progressively shifts traffic from legacy code to the new services. To users and downstream systems, it looks like one system the whole time. Internally, it's a controlled handover in three phases: deploy a routing layer that initially passes everything through unchanged; extract bounded contexts one at a time as independent services, shifting traffic for each; then retire the legacy system once it handles less and becomes a thin shell.&lt;/p&gt;

&lt;p&gt;One distinction worth being precise about: this routing layer is not the same thing as a Graftcode Gateway, which comes up later in this piece. The Strangler Fig routing layer sits in front of the legacy system and directs traffic to it. A Graftcode Gateway isn't a proxy or a traffic router. It's the runtime that hosts a service and exposes its methods to callers. The two solve different problems at different points in the architecture, and conflating them is a common source of confusion when teams start combining the pattern with typed interface tooling.&lt;/p&gt;

&lt;p&gt;The mechanic itself is well understood by now. What separates teams that keep momentum from teams that stall is the decisions around it, which module to extract first, and which supporting pattern to reach for when Strangler Fig alone isn't enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Picking a First Extraction Candidate That Will Not Stall&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The first extraction sets the tone for every one that follows. A hard first candidate teaches the team that extraction is expensive and slow. A good one builds the muscle memory and tooling that make the next four extractions faster.&lt;/p&gt;

&lt;p&gt;A strong first candidate has all five of these properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No shared database tables.&lt;/strong&gt; If a module reads and writes tables other modules also touch, extraction requires resolving data ownership first, which is a separate project on its own.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous-only callers.&lt;/strong&gt; Modules called only synchronously with a direct response are simpler to route behind a facade. Async producers or consumers add broker configuration to the scope.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateless or externally managed state.&lt;/strong&gt; A module holding no in-process state, or storing state in Redis or a dedicated database, can scale independently without session affinity concerns.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A narrow interface.&lt;/strong&gt; A module with three or four public methods can be extracted in days. One with 40 endpoints called from across the codebase needs interface auditing first.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing test coverage.&lt;/strong&gt; Without tests, running legacy and extracted versions in parallel to compare outputs has no baseline to validate against.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strangler Fig handles the extraction mechanism. It doesn't automatically solve the integration problem, the work of making the extracted service and the legacy system talk to each other cleanly. That's where the choice of supporting pattern comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Three Supporting Patterns Solve Three Different Problems&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Strangler Fig rarely works alone in a complex migration. Three supporting patterns each solve a distinct problem that surfaces during extraction. The decision isn't which one is best. It's which problem you actually have.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F044tvo93kk2d8e1yjf0q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F044tvo93kk2d8e1yjf0q.png" alt="Image2 showing tables" width="512" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Branch by Abstraction&lt;/strong&gt; fits when a module has many internal callers. It introduces an interface layer inside the monolith before extraction begins, so callers talk to an interface rather than a concrete implementation. You can then swap the concrete implementation from legacy code to the extracted service without touching any call sites. This matters most when a module is called from dozens of places within the monolith, where updating every call site individually would be disruptive on its own, independent of the extraction itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel Run&lt;/strong&gt; fits when you need to validate behavioral correctness under real traffic before anyone trusts the new path. Requests get sent to both the legacy system and the new service simultaneously, and outputs get compared before committing. Discrepancies are logged but never affect users, since the legacy response is always what gets returned until the new service proves itself. This pattern earns its cost when a silent regression is expensive: payment processing, order fulfillment, anywhere correctness outweighs speed of rollout. Its usual downside is the extra client needed purely for comparison, a second integration surface that exists only to validate, not to serve production traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anti-Corruption Layer&lt;/strong&gt; fits when the legacy domain model shouldn't leak into the new service. It's a translation layer sitting between the legacy system and the new service, converting legacy domain concepts into the new service's domain model. Without it, the new service inherits the legacy system's field names, data shapes, and quirks, and quietly becomes a second legacy system in a new codebase. This matters when the legacy and target domain models genuinely differ: different field semantics, different status representations, different ways of modeling the same concept. When deployed as its own translation service, it typically becomes a cross-language boundary, which is often what makes teams skip building it under timeline pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Common Mistakes That Quietly Compound Integration Cost&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Teams that understand the patterns still hit these repeatedly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shared database between monolith and extracted service.&lt;/strong&gt; Teams move the code first and defer data migration to reduce risk, but "later" rarely arrives, and the shared table becomes permanent coupling. A network hop gets added without achieving actual isolation. Use dual-write or change data capture during the transition, then cut over once the new service's data is validated.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrating too many modules at once.&lt;/strong&gt; Org pressure to show progress across teams leads to parallel extraction, and the integration surface multiplies faster than any one team can track. Every active extraction is a partially bridged system that needs maintenance. Extract sequentially instead, completing one before starting the next.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treating migration bridges as permanent temporary fixes.&lt;/strong&gt; The team that builds the bridge knows it's meant to be replaced. Six months later, a different team owns it, the "temporary" label is long gone from the commit history, and it's carrying production traffic with no test coverage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where Graftcode Changes the Cost of Each Pattern&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The three failure patterns above all trace back to the same root cause: the integration layer between what's extracted and what remains. That's where Graftcode changes the calculation: not by changing which pattern is right for a given situation, but by lowering the cost of executing whichever one you pick.&lt;/p&gt;

&lt;p&gt;Consider a Python ML inference service being extracted from a Java monolith, the same cross-language scenario that makes the third failure pattern above so common. Without a typed communication layer, the Java side needs a hand-written HTTP client, DTO definitions matching the Python service's schema exactly, and error handling the original in-process call never needed. If the Python team renames a field, that mismatch doesn't fail at compile time. It shows up as a validation error or a silent divergence under real production traffic, with no signal until it happens.&lt;/p&gt;

&lt;p&gt;With Graftcode, the calling code looks different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package orders;

import com.graft.maven.inferenceservice.GraftConfig;
import com.graft.maven.inferenceservice.InferenceService;

public class InferenceCaller {
    static {
        GraftConfig.setConfig(System.getenv("GRAFT_CONFIG"));
    }

    public InferenceResult runInference(String modelId, float[] inputVector) throws Exception {
        // Strongly typed: if Python renames a field, this fails at compile time
        return InferenceService.infer(modelId, inputVector);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GraftConfig&lt;/code&gt; reads a single &lt;code&gt;GRAFT_CONFIG&lt;/code&gt; environment variable at startup, a structured string specifying the Graft's name, target host, runtime, and module path. Local development points to &lt;code&gt;host=inMemory&lt;/code&gt; and runs the call in-process with no network hop. Production points to the deployed service's Gateway. The calling code in &lt;code&gt;InferenceCaller&lt;/code&gt; never changes between these two states; only the &lt;code&gt;GRAFT_CONFIG&lt;/code&gt; value does.&lt;/p&gt;

&lt;p&gt;That same mechanism applies across the supporting patterns covered earlier. In Branch by Abstraction, the interface stays exactly what it was; what changes is what sits behind it: a Graft instead of a hand-written client, with the monolith-to-service cutover controlled by &lt;code&gt;GraftConfig&lt;/code&gt; rather than a code change. In Parallel Run, the shadow client used purely for comparison becomes a Graft too, so a response shape change during validation surfaces as a reviewable package update instead of a silent mismatch buried in logs. In Anti-Corruption Layer, when the translation layer is deployed as its own service, both integration hops, monolith to ACL, and ACL to new service, become Grafts instead of hand-rolled HTTP clients, removing the doubled integration cost that usually makes teams skip building a standalone ACL under deadline pressure.&lt;/p&gt;

&lt;p&gt;None of this changes which pattern fits a given extraction. It changes whether the pattern that fits is actually affordable to execute.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;When the Strangler Fig Pattern Is Not the Right Choice&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Incremental extraction isn't universally applicable, and it fails or becomes counterproductive in a few specific situations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tightly coupled UIs.&lt;/strong&gt; Server-side-rendered monoliths where the frontend and backend are inseparable are significantly harder to extract with this approach. A micro-frontend decomposition layer is usually the better first move.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch-first systems.&lt;/strong&gt; Systems built around nightly batch processing rather than request-and-response flows don't benefit from facade-based routing, since the pattern assumes real-time traffic that can be progressively redirected.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broken domain models.&lt;/strong&gt; If the legacy domain model is conceptually wrong rather than just poorly implemented, incremental extraction perpetuates the bad model. Design the target model first, before any extraction begins.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small, isolated systems.&lt;/strong&gt; When a careful rewrite takes weeks, introducing a facade and incremental routing adds overhead the risk reduction doesn't justify.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where to Start With Your Own Modernization Effort&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Legacy modernization rarely fails because a team picked the wrong pattern. Strangler Fig, Branch by Abstraction, Parallel Run, and Anti-Corruption Layer are all well understood and genuinely effective on their own terms. What derails modernization projects is the integration work between what's been extracted and what remains, work that compounds quietly until it consumes more time than the extraction itself was meant to save.&lt;/p&gt;

&lt;p&gt;Start by identifying a first extraction candidate against the five properties covered above: no shared tables, synchronous callers, externally managed state, a narrow interface, and existing test coverage. From there, match the extraction's actual problem to the right supporting pattern rather than defaulting to Strangler Fig alone. To see how Graftcode fits into whichever pattern you choose, explore &lt;a href="https://www.graftcode.com/" rel="noopener noreferrer"&gt;Graftcode&lt;/a&gt; or go directly to the &lt;a href="https://academy.graftcode.com/" rel="noopener noreferrer"&gt;Graftcode Academy&lt;/a&gt; to get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;FAQs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. What is the difference between the Strangler Fig pattern and a big-bang rewrite?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A big-bang rewrite replaces the entire legacy system in one effort, with no incremental value delivery and risk typically measured in years. The Strangler Fig pattern extracts functionality piece by piece while the legacy system stays live, with each extraction independently deployable and reversible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. How do you choose between Branch by Abstraction, Parallel Run, and Anti-Corruption Layer for a given extraction?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The choice depends on the specific problem the extraction has. Branch by Abstraction fits when a module has many internal callers. Parallel Run fits when correctness under real traffic is critical. Anti-Corruption Layer fits when the legacy domain model is different enough from the target model that it shouldn't be allowed to leak in. These aren't mutually exclusive within a single extraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What makes a module a good candidate for the first extraction in a migration?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No shared database tables with other modules, synchronous-only callers, stateless or externally managed state, a narrow interface, and existing test coverage. A candidate missing several of these properties is likely to stall and set the wrong expectations for the extractions that follow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Why do modernization projects usually stall even when the team understands the available patterns?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
 The pattern itself is rarely the problem. What stalls projects is underestimating the integration cost, the work of bridging an extracted service back to the system it came from, especially across language boundaries where a hand-written HTTP client and DTO layer has to be built and maintained indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Is replatforming the same thing as legacy modernization?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Replatforming is one approach within legacy modernization, not the whole category. It means moving an existing system to new infrastructure without changing the code, which is lower risk than re-architecting but doesn't address the underlying architectural constraints that usually caused problems in the first place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. How long does an incremental modernization effort typically take compared to a full rewrite?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It depends entirely on the number of extractions and their complexity, but the more relevant difference is value delivery. A full rewrite delivers nothing until it ships, often years later. Incremental extraction under the Strangler Fig pattern delivers value continuously, with each extracted service live and useful as soon as it's cut over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. What is an Anti-Corruption Layer and when does a team actually need one?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a translation layer between a legacy system and a new service that converts legacy domain concepts into the new service's domain model. A team needs one when the legacy and target domain models genuinely diverge, different field semantics or status representations for the same underlying concept, since skipping it lets legacy constraints leak directly into the new codebase.&lt;/p&gt;

</description>
      <category>modernization</category>
      <category>ai</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>gRPC vs REST: A Decision Guide for Backend Teams</title>
      <dc:creator>Justin</dc:creator>
      <pubDate>Thu, 24 Sep 2026 11:21:09 +0000</pubDate>
      <link>https://dev.to/jusjustin/grpc-vs-rest-a-decision-guide-for-backend-teams-19lk</link>
      <guid>https://dev.to/jusjustin/grpc-vs-rest-a-decision-guide-for-backend-teams-19lk</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;TL;DR&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;REST runs on HTTP/1.1 with JSON and no enforced schema; it's the default for external-facing APIs and interfaces still taking shape. gRPC runs on HTTP/2 with Protobuf and a required contract, built for high-throughput internal services and latency-sensitive workloads like inference pipelines. The real tradeoff is speed and strictness versus universal compatibility, but neither protocol solves the integration maintenance work that piles up as services multiply, work that can eat 30-40% of backend engineering time. This guide breaks down when each protocol fits and what to do about the cost neither one addresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Getting Started: What REST and gRPC Actually Are&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Every service in a distributed system needs a way to talk to another one, and for most teams, that decision defaults to REST without much thought. It's familiar, it runs over plain HTTP, and any client- a browser, a script, another team's service- can call it without installing anything special first. gRPC is the alternative most teams reach for once performance or contract strictness becomes a real constraint on the system, not just a theoretical concern raised in a design review.&lt;/p&gt;

&lt;p&gt;REST is built on HTTP/1.1, the same protocol your browser uses to load a webpage. A service exposes its data as resources at URLs, callers interact using standard HTTP verbs like GET and POST, and data moves back and forth as JSON, plain readable text. Here's what a typical call looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;response = await client.post(
    "http://inference-service/api/v1/inference",
    json={"model": "llama-3", "prompt": prompt, "max_tokens": 512}
)
response.raise_for_status()
return response.json()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any HTTP client can make that call, no generated code, no special tooling, nothing beyond a standard library. That universality is REST's core strength: a new engineer, a third-party partner, or a browser-based frontend can start calling the API immediately.&lt;/p&gt;

&lt;p&gt;The tradeoff shows up on the other side of that convenience. Nothing enforces what shape the response has to take, so nothing stops the provider from renaming max_tokens to max_new_tokens in the next release. The calling service doesn't find out until production, usually as a silent null value where a number should be, or a KeyError that surfaces hours after the actual change shipped.&lt;/p&gt;

&lt;p&gt;gRPC starts from the opposite direction: it requires a contract before any code gets written.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The contract is a .proto file, which defines every method, field, and data type the service interface will expose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A compiler called protoc reads that file and generates typed stubs from it, in whatever language the calling service uses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Once those stubs exist, the calling code calls a generated method as it would any other function in its own codebase, rather than building a request by hand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a provider renames a field in the .proto, the generated stub changes to match, and the calling code fails to compile immediately, catching the break before it reaches production.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the core tradeoff in practice: REST gives you universal compatibility with a loose, self-enforced contract; gRPC gives you a strict, compiler-enforced contract in exchange for real setup cost on both sides.&lt;/p&gt;

&lt;p&gt;Two other differences are worth knowing before the next section gets more technical:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Transport:&lt;/strong&gt; gRPC runs over HTTP/2 instead of HTTP/1.1, which brings multiplexing, multiple requests traveling over a single connection at once, instead of REST's one-request-per-connection default.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Payload format:&lt;/strong&gt; gRPC encodes messages as Protobuf, a compact binary format a computer parses quickly but a human can't read directly, instead of the plain JSON text REST uses, which anyone can open and read without special tooling.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How the Two Protocols Differ at a Technical Level&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Section 2 covered what each protocol is. This section covers what that difference actually costs or saves once a system is running in production, across four dimensions that matter most day-to-day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transport speed under load.&lt;/strong&gt; HTTP/2 gives gRPC multiplexing, header compression, and persistent connections, none of which HTTP/1.1 offers by default. In practice, that means fewer TCP handshakes and lower per-request overhead. For a single request, the difference is negligible. For a service handling hundreds of concurrent requests, like an inference endpoint under real traffic, that gap becomes measurable in p99 latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payload size on the wire.&lt;/strong&gt; JSON is verbose by design; every field name gets repeated in every message. Protobuf, gRPC's binary format, strips that repetition out. A typical Protobuf payload runs 3-10x smaller than the equivalent JSON. At low call volumes, that difference doesn't register. At high throughput, agent orchestration loops, inference pipelines, tool execution chains firing dozens of calls per request, it compounds fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract enforcement.&lt;/strong&gt; This is the schema question from Section 2, restated in terms of what breaks and when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;With REST, schema enforcement is optional. Teams that want it add OpenAPI specs or contract testing on top, extra tooling that has to be maintained separately from the API itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With gRPC, the .proto file is the enforced contract by default. There's no separate tooling to bolt on, the compiler does the enforcement automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The practical difference: a REST contract break surfaces as a runtime bug someone has to trace back to its source. A gRPC contract break surfaces as a compile error, before the code ever ships.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Browser and client compatibility.&lt;/strong&gt; REST works natively in every browser and every HTTP client without exception. gRPC doesn't, because gRPC depends on HTTP/2 trailers, and the browser fetch API doesn't support them. Getting gRPC to work from a browser requires a proxy layer called gRPC-Web to translate between the two. That's not a minor inconvenience; it's a hard technical constraint, and it's the main reason gRPC stays scoped to internal service-to-service communication for most teams rather than reaching all the way to the frontend.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0m84bad5l58sytcxby5z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0m84bad5l58sytcxby5z.png" alt="Graftcode" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Put together, the pattern across all four dimensions is consistent: gRPC trades setup cost and reduced compatibility for speed and strictness, REST trades speed and strictness for universal reach and zero setup. Neither wins outright, they're suited to different jobs, which is exactly what the next two sections walk through.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Why REST Is Still the Right Call for External-Facing APIs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;REST's biggest advantage isn't speed or elegance, it's that it asks nothing of the caller. Any client that speaks plain HTTP can talk to a REST API without installing anything, generating any code, or agreeing to any contract in advance. That low bar is exactly why REST stays the right default in a specific, fairly consistent set of situations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public-facing APIs&lt;/strong&gt; are the clearest case. A browser, a mobile app, a third-party integration, a CLI tool, any of them can call a REST API over plain HTTP with no stubs to generate and no special setup required first. If the API has consumers outside the team that built it, REST remains the default for good reason, nothing else offers that same zero-friction reach.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rapidly evolving interfaces&lt;/strong&gt; favor REST for a different reason. Early-stage products, exploratory internal services, APIs that still change weekly, all of these move faster without a .proto maintenance cycle attached to every change. When the schema itself isn't stable yet, the overhead of formally enforcing it is friction the team doesn't need to carry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human-readable debugging&lt;/strong&gt; is easy to undervalue until it's the thing saving an afternoon. A REST call gone wrong is inspectable immediately in curl, Postman, or browser devtools, no extra step required. A Protobuf payload needs a deserializer and the right .proto file just to make sense of it. For a team onboarding a new engineer, or debugging a cross-service issue under pressure, that difference is felt directly.&lt;/p&gt;

&lt;p&gt;REST is the right fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The API has external consumers, browsers, third parties, or mobile clients&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The interface is still being shaped and changes frequently&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Broad tooling compatibility matters more than raw performance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The team needs fast onboarding and easy debugging over strict guarantees&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One caveat worth flagging: REST's lack of enforced schema is manageable at small scale, but the manual discipline required to prevent contract drift grows right alongside the number of services and consumers. Teams that start on REST often end up investing in OpenAPI specs, contract testing, and SDK generation anyway, doing by hand what gRPC would have enforced automatically from day one.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;When gRPC Is the Right Call for Internal, High-Throughput Services&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Adopting gRPC means paying real setup cost before the first call ever goes out, writing the .proto file, generating stubs, and rerunning that generation step every time the interface changes. That investment pays off in a specific kind of system: one with high call volume, close latency tracking, and a team that controls both ends of the connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-throughput internal services&lt;/strong&gt; are the primary case. An inference service fielding thousands of requests per minute, a tool execution pipeline inside an agent loop, a feature store queried by several downstream services, these are workloads that actually feel the difference between JSON over HTTP/1.1 and Protobuf over HTTP/2. Smaller payloads, persistent connections, and multiplexing don't matter much at low volume, but they compound fast at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Polyglot service meshes&lt;/strong&gt; benefit from gRPC's code generation model specifically. When a Python orchestrator, a Go inference service, and a Java data pipeline all need to share one interface, the .proto file generates consistent typed stubs in each language from a single source of truth. No language can silently misinterpret a field another language set, because the contract is generated, not hand-written per language.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency-sensitive workloads&lt;/strong&gt; are where gRPC's HTTP/2 transport shows up most visibly, and a multi-tool agent loop is the clearest current example. Picture an orchestrator calling three services per request: a retrieval service, a code execution service, and a summarization service.&lt;/p&gt;

&lt;p&gt;With REST, each service call opens its own connection, one after another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async with httpx.AsyncClient() as client:
    retrieval = await client.post("http://retrieval-service/retrieve", json={...})
    execution = await client.post("http://execution-service/execute", json={...})
    summary = await client.post("http://summary-service/summarize", json={...})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With gRPC, all three calls run in parallel over persistent HTTP/2 channels instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;with futures.ThreadPoolExecutor(max_workers=3) as executor:
    r = executor.submit(retrieval_stub.Retrieve, RetrieveRequest(...), timeout=10.0)
    e = executor.submit(execution_stub.Execute, ExecuteRequest(...), timeout=10.0)
    s = executor.submit(summary_stub.Summarize, SummarizeRequest(...), timeout=10.0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At 20 tool calls in a single agent run, even 5ms saved per hop adds up to 100ms off the total response time. That's not a marginal gain, it's the kind of difference that shows up directly in both user-perceived latency and infrastructure cost at scale.&lt;/p&gt;

&lt;p&gt;gRPC is the right fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Services are internal, and the team owns both the client and the server&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call volume is high enough that payload size and connection overhead actually matter&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Multiple languages need to share one strict, enforced interface contract&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Streaming is part of the design, token streaming, live status updates, bidirectional flows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An agent loop is calling several downstream services in parallel per request&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Integration Maintenance Cost That Accumulates Regardless of Protocol&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Picking a protocol answers the transport question. It doesn't answer what happens after that: the ongoing work of keeping every service integration in sync as the system grows. Writing HTTP clients, keeping DTO schemas aligned, and managing stub regeneration, together these can consume 30-40% of backend engineering time, and the cost compounds with every service added.&lt;/p&gt;

&lt;p&gt;With REST, every integration means writing and maintaining an HTTP client by hand, wrapping request construction, error handling, and response parsing around each service the team calls.&lt;/p&gt;

&lt;p&gt;With gRPC, the stub handles transport automatically, but the .proto maintenance cycle becomes the team's responsibility instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Every time the inference service changes its interface:
protoc --python_out=. --grpc_python_out=. inference.proto
# Commit the regenerated stubs
# Update every calling service that imports them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here's what interface drift looks like when that step gets skipped. A provider renames max_tokens to max_new_tokens in the .proto. Without regenerating stubs, the calling code fails silently in production. After regenerating, it fails loudly at compile time, TypeError: unexpected keyword, caught before it ever ships.&lt;/p&gt;

&lt;p&gt;Both paths land on the same underlying problem: keeping a calling service in sync with its provider is manual work, and that work scales with the number of services in the system, not with how complex any individual integration is.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where Graftcode Fits In&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Section 6 reached the same conclusion for both protocols: neither removes the integration work sitting on top of the transport decision. Graftcode is built to address that layer directly, not by replacing REST or gRPC, but by sitting alongside whatever's already running.&lt;/p&gt;

&lt;p&gt;Instead of writing an HTTP client or running protoc, a calling service installs a Graft, a strongly-typed interface generated for that specific provider, through its normal package manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install --index-url https://grft.dev/your-project-id inference-service@1.0.0
pip install --index-url https://grft.dev/your-project-id retrieval-service@1.0.0
pip install --index-url https://grft.dev/your-project-id summary-service@1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three-service agent loop from Section 5, the one that needed either sequential REST calls or a manually parallelized gRPC stub setup, becomes a direct function call to each service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def run_agent_loop(query: str, prompt: str, text: str) -&amp;gt; dict:
    retrieval = RetrievalService.retrieve(query=query)
    inference = InferenceService.run_inference(model="llama-3", prompt=prompt, max_tokens=512)
    summary = SummaryService.summarize(text=text)
    return {"retrieval": retrieval, "inference": inference, "summary": summary}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No HTTP client, no DTO, no stub regeneration step. If a provider changes its interface, the change surfaces as a package update, caught at compile time rather than at runtime, the same protection gRPC offers, without the .proto cycle REST and gRPC both require the team to maintain by hand.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpe7x7y0bg0i5vnhcufs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkpe7x7y0bg0i5vnhcufs.png" alt="Graftcode" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The routing itself is controlled by GraftConfig, which lives inside the calling service, not in a separate gateway or sidecar. It decides whether a given call runs in-process, never leaving the calling service at all, or as a remote call to the deployed provider, and that choice is set through an environment variable, a config file, or directly on the Graft itself. Switching a service from local development to a remote deployed dependency doesn't require touching application code.&lt;/p&gt;

&lt;p&gt;On performance, Graftcode's own Performance Lab ran a large-payload test, 5,000 data points per call, all three approaches on the same .NET runtime over HTTP/2, network latency excluded. Graftcode completed in 22ms, gRPC unary calls in 53ms, and REST in 1,245ms, putting Graftcode 98.2% faster than REST on that workload with a fraction of the CPU overhead. According to Graftcode, removing the HTTP client and stub layer entirely also reduces token usage for AI coding assistants by 30-60% on integration-heavy services, since there's no protocol boilerplate left for the assistant to parse and hold in context.&lt;/p&gt;

&lt;p&gt;Worth being direct about the constraint: Graftcode is still in Alpha, and primitive wrapper types like Date, Guid, and TimeSpan aren't supported yet, they need to be passed as string or int instead. That's the kind of limitation worth checking against current documentation before it factors into an adoption decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;A Practical Decision Framework for Backend Teams&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Protocol decisions rarely happen in isolation. They depend on who's calling the API, how much the interface is still changing, and what the team can realistically keep maintained. Three scenarios cover most real cases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A SaaS platform with a public API and third-party integrations&lt;/strong&gt; should default to REST. Browsers and third-party clients need zero setup to start consuming it, and that reach outweighs whatever performance gain switching protocols might offer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An ML inference service called by several internal services&lt;/strong&gt; fits gRPC well. The interface is stable, call volume is high, latency is tracked closely, and the team controls both ends of the connection, which is exactly the profile Section 5 laid out. The .proto maintenance cost is worth paying for the strict contract and Protobuf's performance at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A microservices team with a steadily growing number of internal services&lt;/strong&gt; is often looking at the wrong variable. If meaningful sprint time is going to integration code, HTTP clients, DTO sync, stub regeneration, the protocol choice isn't the actual bottleneck. This is the scenario Sections 6 and 7 speak to directly: the Performance Lab benchmark from Section 7 puts real numbers behind it, Graftcode at 22ms against gRPC's 53ms and REST's 1,245ms on the same workload. At 200k RPS on Azure Standard_D16s_v5, Graftcode's own estimate puts that gap at roughly \$1.64B in annual compute savings, a figure worth verifying against current published data before repeating it as settled fact.&lt;/p&gt;

&lt;p&gt;The signals that point toward each option, side by side:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consumers:&lt;/strong&gt; external and browser-facing points to REST, internal-only points to gRPC or Graftcode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Schema stability:&lt;/strong&gt; still evolving points to REST, stable and enforced points to gRPC or Graftcode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Call volume:&lt;/strong&gt; moderate points to REST, high-throughput points to gRPC, very high with minimal CPU overhead points to Graftcode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Languages involved:&lt;/strong&gt; single language or standard HTTP clients points to REST, polyglot with generated stubs points to gRPC, polyglot with no stubs needed points to Graftcode&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration overhead:&lt;/strong&gt; manageable points to REST, growing with service count points to gRPC, already consuming real team time points to Graftcode&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Where Should You Start&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Choosing between REST and gRPC comes down to who's calling the API and how much performance and contract strictness actually matter for that specific service, REST for external-facing APIs and evolving interfaces, gRPC for internal, high-throughput systems where the team controls both ends. But that choice only answers the transport question. The integration maintenance work sitting on top of it, hand-written HTTP clients, DTO sync, stub regeneration, accumulates regardless of which protocol a team picks, and it's the cost that keeps growing quietly while the protocol debate gets all the attention.&lt;/p&gt;

&lt;p&gt;For teams where that integration overhead is already visible in sprint velocity, Graftcode addresses that layer directly without requiring a rip-and-replace of what's already running. It installs alongside existing REST or gRPC services, one service boundary at a time, with compile-time contract enforcement and routing controlled through GraftConfig rather than hand-maintained client code. Full technical detail and the Performance Lab benchmarks are at graftcode.com, and the monolith-to-microservices use case is worth a look for teams currently mid-migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;FAQs&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Can gRPC and REST coexist in the same microservices architecture?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Yes, and most production systems already do this. External-facing APIs stay on REST for broad client compatibility, while internal service-to-service communication moves to gRPC for performance and contract enforcement. An API gateway at the edge typically handles the translation between the two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. What's the actual performance difference between REST and gRPC at high call volumes?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;At low volumes, the gap is small enough to ignore. At high throughput, thousands of requests per second, Protobuf's binary encoding and HTTP/2's multiplexing produce measurable latency and CPU differences, with gRPC benchmarks consistently showing lower p99 latency under load, particularly for the small, frequent payloads typical of internal service calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Is gRPC a realistic option for browser-based frontend applications?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Not natively. Browsers don't support the HTTP/2 trailers gRPC depends on, so reaching a browser requires a proxy layer called gRPC-Web to translate between the two. For most teams, that added infrastructure makes REST the practical default for anything browser-facing, with gRPC scoped to internal communication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. How does Graftcode handle routing compared to gRPC's service discovery?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;GraftConfig, which lives inside the calling service, controls where a remote call goes and points to the Graftcode Gateway running for that provider service. Load balancing itself still sits at the infrastructure layer, the same Kubernetes service or load balancer a gRPC or REST deployment would use, since Graftcode replaces the integration code layer, not the infrastructure underneath it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Can Graftcode be adopted alongside an existing REST or gRPC service during a migration?&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Yes, this is one of the more common ways teams bring it in. A team can extract one service, run its Graftcode Gateway for that service, and set the calling code to run in-process locally while routing remotely in staging and production, without changing the application code. Existing REST or gRPC services keep running unchanged elsewhere in the system.&lt;/p&gt;

</description>
      <category>grpc</category>
      <category>webdev</category>
      <category>decisionguide</category>
      <category>ai</category>
    </item>
    <item>
      <title>Agencies that market to software engineers and developers</title>
      <dc:creator>Justin</dc:creator>
      <pubDate>Mon, 16 Mar 2026 10:07:10 +0000</pubDate>
      <link>https://dev.to/jusjustin/how-to-hire-a-developer-marketing-agency-for-my-devtool-startup-9cg</link>
      <guid>https://dev.to/jusjustin/how-to-hire-a-developer-marketing-agency-for-my-devtool-startup-9cg</guid>
      <description>&lt;p&gt;Sure you must have heard how marketing to developers is notoriously difficult. Not because developers are impossible to reach — but because they ignore most marketing.&lt;/p&gt;

&lt;p&gt;Engineers care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;solving problems&lt;/li&gt;
&lt;li&gt;learning new tools&lt;/li&gt;
&lt;li&gt;seeing working examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means developer marketing looks very different from typical SaaS marketing.&lt;/p&gt;

&lt;p&gt;Instead of ads and slogans, you usually need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tutorials&lt;/li&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;architecture guides&lt;/li&gt;
&lt;li&gt;open-source examples&lt;/li&gt;
&lt;li&gt;deep technical blogs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Over the past few years, a handful of agencies have emerged that specialize in this space.&lt;/p&gt;

&lt;p&gt;Here are a few worth knowing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrasity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="//infrasity.com"&gt;Infrasity &lt;/a&gt;focuses on developer infrastructure companies and SaaS platforms.&lt;/p&gt;

&lt;p&gt;Their work tends to revolve around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;developer tutorials&lt;/li&gt;
&lt;li&gt;technical blogs&lt;/li&gt;
&lt;li&gt;integration guides&lt;/li&gt;
&lt;li&gt;DevOps and infrastructure topics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What makes them interesting is that much of the content is written by engineers, which helps avoid the “marketing fluff” problem that developers hate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Draft.dev&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Draft.dev connects companies with developer writers.&lt;/p&gt;

&lt;p&gt;They produce technical articles covering topics like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;programming languages&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;cloud infrastructure&lt;/li&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many devtool startups use them to scale technical blog production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Animalz&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Animalz is well known in the SaaS world for high-quality long-form content.&lt;/p&gt;

&lt;p&gt;They’re not specifically developer-focused, but they’ve worked with many technical companies and understand complex SaaS products.&lt;/p&gt;

&lt;p&gt;Omniscient Digital&lt;/p&gt;

&lt;p&gt;Omniscient focuses heavily on SEO-driven content strategy.&lt;/p&gt;

&lt;p&gt;For developer products targeting search traffic around tutorials or comparisons, this approach can work well.&lt;/p&gt;

&lt;p&gt;Developer relations consultancies&lt;/p&gt;

&lt;p&gt;Some companies also partner with DevRel-focused agencies, which handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;community engagement&lt;/li&gt;
&lt;li&gt;developer programs&lt;/li&gt;
&lt;li&gt;conferences&lt;/li&gt;
&lt;li&gt;technical advocacy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This can complement content-focused marketing efforts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best developer marketing rarely feels like marketing. It looks more like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;documentation&lt;/li&gt;
&lt;li&gt;tutorials&lt;/li&gt;
&lt;li&gt;engineering blog posts&lt;/li&gt;
&lt;li&gt;open-source examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agencies that understand this difference tend to produce the content developers actually trust.&lt;/p&gt;

</description>
      <category>developers</category>
      <category>marketing</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
  </channel>
</rss>
