<?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: Vinícius S. de Pontes</title>
    <description>The latest articles on DEV Community by Vinícius S. de Pontes (@vsdepontes).</description>
    <link>https://dev.to/vsdepontes</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%2F2488997%2Fdc3b766b-5d5a-4668-8ee3-2623421bd40c.png</url>
      <title>DEV Community: Vinícius S. de Pontes</title>
      <link>https://dev.to/vsdepontes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vsdepontes"/>
    <language>en</language>
    <item>
      <title>When an SDK Is Better Than Documentation</title>
      <dc:creator>Vinícius S. de Pontes</dc:creator>
      <pubDate>Sat, 05 Sep 2026 19:31:23 +0000</pubDate>
      <link>https://dev.to/vsdepontes/when-an-sdk-is-better-than-documentation-13hc</link>
      <guid>https://dev.to/vsdepontes/when-an-sdk-is-better-than-documentation-13hc</guid>
      <description>&lt;p&gt;When I implemented &lt;a href="https://vsdepontes.com/distributed-memoization-avoid-repeating-expensive-work" rel="noopener noreferrer"&gt;distributed memoization&lt;/a&gt;, using the pattern correctly required more than just calling an API.&lt;/p&gt;

&lt;p&gt;A consumer had to check the result store, generate the right key, deserialize the result, handle a cache miss, call the original service, and decide what to do if the cache was unavailable.&lt;/p&gt;

&lt;p&gt;I could document all of that.&lt;/p&gt;

&lt;p&gt;But then every consumer would have to implement it.&lt;/p&gt;

&lt;p&gt;Instead, I created an SDK.&lt;/p&gt;

&lt;p&gt;An SDK, or Software Development Kit, is a package that gives developers a simpler interface for interacting with a system or capability. It can wrap APIs, configuration, authentication, retries, data transformations, and other implementation details behind code that is easier to use consistently.&lt;/p&gt;

&lt;p&gt;This becomes especially valuable in a microservices architecture, where several independent services may consume the same capability. Without a shared abstraction, small implementation differences can easily spread across the system.&lt;/p&gt;

&lt;p&gt;In this case, the SDK wasn't only about code reuse. It turned the expected way of using the architecture into code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiding the integration
&lt;/h2&gt;

&lt;p&gt;Without an SDK, a consumer might end up with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createResultKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sales-report&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cachedResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resultStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedResult&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cachedResult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reportService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't look particularly complicated.&lt;/p&gt;

&lt;p&gt;The problem appears when five services need to do it.&lt;/p&gt;

&lt;p&gt;One might normalize the input differently. Another might forget the function version. One might fail the whole request when the result store is unavailable, while another falls back to the service.&lt;/p&gt;

&lt;p&gt;In a microservices system, this is particularly easy to run into. Each service is developed and deployed independently, often by different teams and on different schedules. If every service implements the integration itself, those implementations can slowly diverge.&lt;/p&gt;

&lt;p&gt;Even with good documentation, we're still asking every consumer to understand and reproduce the same decisions.&lt;/p&gt;

&lt;p&gt;With an SDK, the consumer can see something closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reportClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateSalesReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, the SDK handles the result lookup and falls back to the service when necessary.&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%2Fmh66oyap5iv88ox5g4f3.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%2Fmh66oyap5iv88ox5g4f3.png" alt="Diagram showing a consumer sending a request through an SDK. The SDK first checks a result store for a cached response. If no cached result is found, a cache miss occurs and the SDK forwards the request to the service." width="632" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The consumer doesn't need to know how keys are generated or how the fallback works.&lt;/p&gt;

&lt;p&gt;It only needs to know how to generate a report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code reuse is only part of the value
&lt;/h2&gt;

&lt;p&gt;Shared functions could also remove some duplication.&lt;/p&gt;

&lt;p&gt;An SDK goes a little further because it can define the boundary through which consumers interact with a capability.&lt;/p&gt;

&lt;p&gt;For distributed memoization, that meant centralizing things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;input normalization and key generation;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;serialization and deserialization;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cache lookup behavior;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;timeouts and fallback rules;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;connection and configuration details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authentication and authorization mechanisms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If one of these decisions changes, consumers don't necessarily need to change.&lt;/p&gt;

&lt;p&gt;Maybe the key format moves from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales-report:v3:{input-hash}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;result:sales-report:v4:{input-hash}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is an implementation detail of the memoization mechanism. It shouldn't become an implementation detail of every service using it.&lt;/p&gt;

&lt;p&gt;The SDK creates a place for that knowledge to live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the expected path easier
&lt;/h2&gt;

&lt;p&gt;Documentation describes how something should be used.&lt;/p&gt;

&lt;p&gt;An SDK can make that usage the default.&lt;/p&gt;

&lt;p&gt;This distinction becomes more useful as an architecture grows.&lt;/p&gt;

&lt;p&gt;Suppose ten services need to call the same internal platform. We can give every team documentation explaining authentication, retries, headers, error mapping, timeouts, and endpoint conventions.&lt;/p&gt;

&lt;p&gt;Or we can expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PlatformClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Documentation is still useful. Consumers should understand the important behavior and tradeoffs.&lt;/p&gt;

&lt;p&gt;They just shouldn't have to reproduce infrastructure code to use the platform correctly.&lt;/p&gt;

&lt;p&gt;This also makes standardization less dependent on people remembering conventions.&lt;/p&gt;

&lt;p&gt;If authentication changes, the SDK can change.&lt;/p&gt;

&lt;p&gt;If every request should include new telemetry, the SDK can add it.&lt;/p&gt;

&lt;p&gt;If a timeout policy needs to be adjusted, there is one implementation instead of several slightly different ones spread across the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What belongs behind an SDK?
&lt;/h2&gt;

&lt;p&gt;Not every abstraction needs its own package.&lt;/p&gt;

&lt;p&gt;A small helper used twice is probably just a small helper.&lt;/p&gt;

&lt;p&gt;An SDK becomes more interesting when several consumers interact with the same capability, and there are meaningful rules around that interaction.&lt;/p&gt;

&lt;p&gt;Internal APIs are an obvious example, but the same idea can apply to caching, authentication, feature flags, event publishing, storage, business rules engines, or any other shared platform capability.&lt;/p&gt;

&lt;p&gt;The important part is finding the right boundary.&lt;/p&gt;

&lt;p&gt;An SDK that exposes every internal detail doesn't abstract much. At the other extreme, an SDK that tries to anticipate every possible use case can become more complicated than the system it hides.&lt;/p&gt;

&lt;p&gt;A good SDK usually gives consumers the concepts they actually care about while keeping infrastructure decisions underneath.&lt;/p&gt;

&lt;p&gt;In the memoization example, the consumer cares about generating the report.&lt;/p&gt;

&lt;p&gt;It doesn't care how a deterministic input becomes a cache key.&lt;/p&gt;

&lt;p&gt;That difference is a useful place to draw the boundary.&lt;/p&gt;

&lt;p&gt;SDKs are therefore more than a way to avoid copying code.&lt;/p&gt;

&lt;p&gt;Used at the right boundary, they let us implement an architectural decision once and give consumers a smaller, harder-to-misuse interface to it.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>architecture</category>
      <category>devex</category>
      <category>software</category>
    </item>
    <item>
      <title>Distributed Memoization: Avoid Repeating Expensive Work</title>
      <dc:creator>Vinícius S. de Pontes</dc:creator>
      <pubDate>Fri, 21 Aug 2026 16:08:13 +0000</pubDate>
      <link>https://dev.to/vsdepontes/distributed-memoization-avoid-repeating-expensive-work-2ib4</link>
      <guid>https://dev.to/vsdepontes/distributed-memoization-avoid-repeating-expensive-work-2ib4</guid>
      <description>&lt;p&gt;Some operations produce the same output when given the same input.&lt;/p&gt;

&lt;p&gt;Without caching, every request still reaches the service, accesses its dependencies, and performs the same work again. This happens even when the result was calculated moments ago.&lt;/p&gt;

&lt;p&gt;I encountered this in a reporting service. The main database query was already accelerated by a materialized view, but every request still had to retrieve, validate, process, and enrich the data.&lt;/p&gt;

&lt;p&gt;Caching the final result was faster and cheaper. Instead of optimizing one dependency, we could skip the entire operation.&lt;/p&gt;

&lt;p&gt;That experience changed how I think about caching: sometimes the most useful thing to cache is not the data, but the completed work.&lt;/p&gt;

&lt;p&gt;This is a form of &lt;strong&gt;distributed memoization&lt;/strong&gt;.&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%2Fjpl3s8b83t69mnbdjauv.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%2Fjpl3s8b83t69mnbdjauv.png" alt="Every request passes through the reporting service and reads from a fast materialized view before the data is validated, processed, and enriched. Caching the data accelerates retrieval, but the rest of the processing pipeline still runs for every request." width="" height=""&gt;&lt;/a&gt;&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%2F095bikiddhrjl1wfxaep.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%2F095bikiddhrjl1wfxaep.png" alt="A request first checks a Valkey or Redis result store. A cache hit returns the completed result immediately. On a miss, the reporting service reads the materialized view, validates, processes, and enriches the data, returns a fresh result, and stores it for future requests." width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching the operation
&lt;/h2&gt;

&lt;p&gt;Traditional caching is often described in terms of database records or query responses.&lt;/p&gt;

&lt;p&gt;That helps with data access, but the application may still need to perform the same processing afterward. Distributed memoization looks at the problem differently. It stores the output of an operation.&lt;/p&gt;

&lt;p&gt;Consider an operation that generates a sales report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;generateSalesReport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;companyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;company-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-07&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;currency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;USD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generating the report may still require retrieving the precomputed data, validating it, applying business rules, enriching it with data from other sources, and formatting the response.&lt;/p&gt;

&lt;p&gt;The database query was already fast, but the complete operation still had a cost. If multiple users requested the same report, storing the final result avoided repeating the entire processing pipeline.&lt;/p&gt;

&lt;p&gt;The consumer checks a result store first. On a hit, it returns the completed report. On a miss, it calls the original service, which generates and stores the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying a result
&lt;/h2&gt;

&lt;p&gt;The result can be stored using a key derived from the operation and its input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;result:sales-report:v3:7f83b165...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A practical key contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{operation}:{function-version}:{input-hash}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function version prevents a new implementation from returning results produced by older code.&lt;/p&gt;

&lt;p&gt;The input should be normalized before hashing. Normalization means converting equivalent inputs into a single, consistent representation.&lt;/p&gt;

&lt;p&gt;For example, these objects contain the same data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"companyId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"company-123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2026-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"period"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"2026-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"companyId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"company-123"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A direct hash of their serialized forms could produce different keys because the fields appear in a different order. Normalization can sort object fields and standardize dates and casing.&lt;/p&gt;

&lt;p&gt;After normalization, equivalent inputs produce the same hash and reuse the same stored result.&lt;/p&gt;

&lt;h2&gt;
  
  
  One possible implementation
&lt;/h2&gt;

&lt;p&gt;The result store could be Valkey, Redis, or another key-value store with suitable latency and expiration support.&lt;/p&gt;

&lt;p&gt;In a serverless architecture, the flow might look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The consumer checks Valkey.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;On a miss, it calls API Gateway.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API Gateway invokes Lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda reads any required data and calculates the result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda stores the result in Valkey.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Future requests read it directly.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same pattern works without serverless components. Lambda could be a service running in a container, a background worker, or an application process. Similarly, the data source could be a database, an external API, a file, an AI model, or another dependency used by the operation.&lt;/p&gt;

&lt;p&gt;The important distinction is between the &lt;strong&gt;fast path&lt;/strong&gt;, which returns completed work, and the &lt;strong&gt;computation path&lt;/strong&gt;, which produces results that do not exist yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping writes centralized
&lt;/h2&gt;

&lt;p&gt;Consumers only need read access to the result store.&lt;/p&gt;

&lt;p&gt;The service responsible for the operation remains the only writer. This prevents consumers from publishing arbitrary results and keeps the calculation logic in one place.&lt;/p&gt;

&lt;p&gt;A shared client library can hide the lookup and fallback behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateSalesReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, the library:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Builds the key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Checks the result store.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Returns the stored value on a hit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calls the service on a miss.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It can also handle serialization, timeouts, and fallback consistently across consumers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Invalidating results
&lt;/h2&gt;

&lt;p&gt;An operation may depend on data that changes over time.&lt;/p&gt;

&lt;p&gt;The sales report, for example, may use conversion rates to present transactions in a selected currency. When a rate changes, the application should explicitly invalidate reports calculated with that currency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateConversionRate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conversionRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;conversionRateRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;reportCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateByCurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;conversionRate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetCurrency&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;conversionRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The invalidation can happen directly in the write flow or through an event handler triggered after the data changes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;invalidateByCurrency&lt;/code&gt; can use an index that associates each currency with the cached reports that depend on it.&lt;/p&gt;

&lt;p&gt;The next request will miss the cache, generate the report again, and store the updated result. A TTL can still be used to remove results that are no longer requested, but it is a secondary mechanism rather than the main invalidation strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it fits
&lt;/h2&gt;

&lt;p&gt;Distributed memoization is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The operation is deterministic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The same inputs appear frequently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Results are relatively small.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The operation or its dependencies have a meaningful cost.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Possible use cases include pricing calculations, reports, data transformations, external API calls, machine-learning inference, and business rules engine evaluations.&lt;/p&gt;

&lt;p&gt;I would not add this to a cheap operation or one where inputs rarely repeat. In those cases, the result store becomes another dependency without avoiding enough work to justify it. The pattern becomes useful when the hit rate is high enough for a lookup to regularly replace an entire chain of computation and network calls.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>distributedsystems</category>
      <category>systemdesign</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
