680 lines
25 KiB
HTML
680 lines
25 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.8.1/github-markdown.min.css"
|
|
integrity="sha512-BrOPA520KmDMqieeM7XFe6a3u3Sb3F1JBaQnrIAmWg3EYrciJ+Qqe6ZcKCdfPv26rGcgTrJnZ/IdQEct8h3Zhw=="
|
|
crossorigin="anonymous"
|
|
referrerpolicy="no-referrer"
|
|
/>
|
|
<title>haunt98 posts</title>
|
|
</head>
|
|
<style>
|
|
.markdown-body {
|
|
box-sizing: border-box;
|
|
min-width: 200px;
|
|
max-width: 980px;
|
|
margin: 0 auto;
|
|
padding: 45px;
|
|
font-family:
|
|
Shantell Sans Normal,
|
|
Rec Mono Casual,
|
|
SF Pro,
|
|
Inter,
|
|
sans-serif;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.markdown-body pre {
|
|
font-family:
|
|
Berkeley Mono,
|
|
IBM Plex Mono,
|
|
SF Mono,
|
|
Jetbrains Mono,
|
|
monospace;
|
|
}
|
|
|
|
@media (max-width: 767px) {
|
|
.markdown-body {
|
|
padding: 15px;
|
|
}
|
|
}
|
|
</style>
|
|
<body class="markdown-body">
|
|
<h2>
|
|
<a href="index.html"><code>~</code></a>
|
|
</h2>
|
|
<div class="markdown-heading">
|
|
<h1 class="heading-element">Backend Thinking</h1>
|
|
<a
|
|
id="user-content-backend-thinking"
|
|
class="anchor"
|
|
aria-label="Permalink: Backend Thinking"
|
|
href="#backend-thinking"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<div class="markdown-heading">
|
|
<h2 class="heading-element">Backend Role</h2>
|
|
<a
|
|
id="user-content-backend-role"
|
|
class="anchor"
|
|
aria-label="Permalink: Backend Role"
|
|
href="#backend-role"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>Transform business requirements to action, which usually involves:</p>
|
|
<ul>
|
|
<li>
|
|
Service:
|
|
<ul>
|
|
<li>
|
|
ZaloPay use microservices architecture, mostly written using Go and
|
|
Java
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
API:
|
|
<ul>
|
|
<li>HTTP (Client-Server) and GRPC (Server-Server)</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
Database/Cache/Storage/Message Broker
|
|
<ul>
|
|
<li>MySQL/Redis/S3/Kafka</li>
|
|
<li>CRUD</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
Docs
|
|
<ul>
|
|
<li>
|
|
Mostly design notes and diagrams which show how to implement
|
|
business requirements
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p>After successfully do all of that, next step is:</p>
|
|
<ul>
|
|
<li>
|
|
Testing
|
|
<ul>
|
|
<li>Unit tests, Integration tests</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
Observation
|
|
<ul>
|
|
<li>Log</li>
|
|
<li>Metrics</li>
|
|
<li>Tracing</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p>
|
|
In ZaloPay, each team has its own responsibilities/domains, aka many
|
|
different services.
|
|
</p>
|
|
<p>
|
|
Ideally each team can choose custom backend techstack if they want, but
|
|
mostly boils down to Java or Go. Some teams use Python for scripting, data
|
|
processing, ...
|
|
</p>
|
|
<p>
|
|
<em>Example</em>: Team UM (User Management) has 10+ Java services and 30+
|
|
Go services.
|
|
</p>
|
|
<p>
|
|
The question is for each new business requirements, what should we do:
|
|
</p>
|
|
<ul>
|
|
<li>Create new services with new APIs?</li>
|
|
<li>Add new APIs to existing services?</li>
|
|
<li>Update existing APIs?</li>
|
|
<li>Change configs?</li>
|
|
<li>Don't do anything?</li>
|
|
</ul>
|
|
<p>
|
|
<em>Example</em>: Business requirements says: Must match/compare user EKYC
|
|
data with Bank data (name, dob, id, ...).
|
|
</p>
|
|
<div class="markdown-heading">
|
|
<h2 class="heading-element">Technical side</h2>
|
|
<a
|
|
id="user-content-technical-side"
|
|
class="anchor"
|
|
aria-label="Permalink: Technical side"
|
|
href="#technical-side"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>Backend services talk to Frontend, and talk to each other.</p>
|
|
<p>How do they communicate?</p>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">API</h3>
|
|
<a
|
|
id="user-content-api"
|
|
class="anchor"
|
|
aria-label="Permalink: API"
|
|
href="#api"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>
|
|
<strong>First</strong> is through API, this is the direct way, you send a
|
|
request then you wait for response.
|
|
</p>
|
|
<p><strong>HTTP</strong></p>
|
|
<ul>
|
|
<li>Use HTTP Method GET/POST/…</li>
|
|
<li>HTTP responses status code</li>
|
|
<li>
|
|
ZaloPay rule
|
|
<ul>
|
|
<li>Only return code 200</li>
|
|
<li>Response body is only JSON</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p><strong>GRPC</strong></p>
|
|
<ul>
|
|
<li>Use proto file as contract</li>
|
|
<li>
|
|
GRPC status code
|
|
<ul>
|
|
<li>OK</li>
|
|
<li>INVALID_ARGUMENT</li>
|
|
<li>INTERNAL …</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p>
|
|
There are no hard rules on how to design APIs, only some best practices,
|
|
like REST API, ...
|
|
</p>
|
|
<p>Correct answer will always be: "It depends". Depends on:</p>
|
|
<ul>
|
|
<li>
|
|
Your audience (android, ios, web client or another internal service)
|
|
</li>
|
|
<li>Your purpose (allow to do what?)</li>
|
|
<li>Your current techstack (technology limitation?)</li>
|
|
<li>Your team (bias, prefer, ...?)</li>
|
|
<li>...</li>
|
|
</ul>
|
|
<p>Why do we use HTTP for Client-Server and GRPC for Server-Server?</p>
|
|
<ul>
|
|
<li>
|
|
HTTP for Client-Server is pretty standard. Easy for client to debug, ...
|
|
</li>
|
|
<li>
|
|
Before ZaloPay switch to GRPC for Server-Server, we use HTTP. The reason
|
|
for switch is mainly performance.
|
|
</li>
|
|
</ul>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">Message Broker</h3>
|
|
<a
|
|
id="user-content-message-broker"
|
|
class="anchor"
|
|
aria-label="Permalink: Message Broker"
|
|
href="#message-broker"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>
|
|
<strong>Second</strong> way is by Message Broker, the most well known is
|
|
Kafka.
|
|
</p>
|
|
<p>Main idea is decoupling.</p>
|
|
<p>
|
|
Imaging service A need to call services B, C, D, E after doing some
|
|
action, but B just died. We must handle B errors gracefully if B is not
|
|
that important (not affect main flow of A). Imaging not only B, but multi
|
|
B, like B1, B2, B3, ... Bn, this is so depressed to continue.
|
|
</p>
|
|
<p>Message Broker is a way to detach B from A.</p>
|
|
<p>
|
|
Dumb explain be like: each time A do something, A produces message to
|
|
Message Broker, than A forgets about it. Then all B1, B2 can consume A's
|
|
message if they want and do something with it, A does not know and does
|
|
not need to know about it.
|
|
</p>
|
|
<div class="highlight highlight-source-mermaid">
|
|
<pre><span class="pl-k">sequenceDiagram</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">A</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">B</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">C</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">D</span>
|
|
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">B</span><span class="pl-k">:</span> <span class="pl-s">do something</span>
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">C</span><span class="pl-k">:</span> <span class="pl-s">do something</span>
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">D</span><span class="pl-k">:</span> <span class="pl-s">do something</span></pre>
|
|
</div>
|
|
<div class="highlight highlight-source-mermaid">
|
|
<pre><span class="pl-k">sequenceDiagram</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">A</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">B</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">C</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">D</span>
|
|
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">B</span><span class="pl-k">:</span> <span class="pl-s">do something</span>
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">C</span><span class="pl-k">:</span> <span class="pl-s">do something</span>
|
|
<span class="pl-ent">A </span><span class="pl-k">-x</span> <span class="pl-ent">D</span><span class="pl-k">:</span> <span class="pl-s">do something but failed</span></pre>
|
|
</div>
|
|
<div class="highlight highlight-source-mermaid">
|
|
<pre><span class="pl-k">sequenceDiagram</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">A</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">B</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">C</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">D</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">Kafka</span>
|
|
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">B</span><span class="pl-k">:</span> <span class="pl-s">do something</span>
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">C</span><span class="pl-k">:</span> <span class="pl-s">do something</span>
|
|
<span class="pl-ent">A </span><span class="pl-k">->></span> <span class="pl-ent">Kafka</span><span class="pl-k">:</span> <span class="pl-s">produce message</span>
|
|
<span class="pl-ent">D </span><span class="pl-k">->></span> <span class="pl-ent">Kafka</span><span class="pl-k">:</span> <span class="pl-s">consume message</span>
|
|
<span class="pl-ent">D </span><span class="pl-k">->></span> <span class="pl-ent">D</span><span class="pl-k">:</span> <span class="pl-s">do something</span></pre>
|
|
</div>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">Tip</h3>
|
|
<a
|
|
id="user-content-tip"
|
|
class="anchor"
|
|
aria-label="Permalink: Tip"
|
|
href="#tip"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<ul>
|
|
<li>
|
|
Whatever you design, you stick with it consistently. Don't use different
|
|
name for same object/value in your APIs.
|
|
</li>
|
|
<li>
|
|
Don't trust client blindly, everything can be fake, everything must be
|
|
validated. We can not know the request is actually from our client or
|
|
some hacker computer. (Actually we can but this is out of scope, and
|
|
require lots of advance work)
|
|
</li>
|
|
<li>
|
|
Don't delete/rename/change old fields because you want and you can,
|
|
please think it through before do it. Because back compatibility is very
|
|
hard, old apps should continue to function if user don't upgrade. Even
|
|
if we rollout new version, it takes time.
|
|
</li>
|
|
</ul>
|
|
<p>
|
|
<strong>Pro tip</strong>: Use proto to define models (if you can) to take
|
|
advantage of detecting breaking changes.
|
|
</p>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">References</h3>
|
|
<a
|
|
id="user-content-references"
|
|
class="anchor"
|
|
aria-label="Permalink: References"
|
|
href="#references"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<ul>
|
|
<li>
|
|
<a
|
|
href="https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/"
|
|
rel="nofollow"
|
|
>Best practices for REST API design</a
|
|
>
|
|
<ul>
|
|
<li>
|
|
<a href="https://docs.zalopay.vn/v2/" rel="nofollow">ZaloPay API</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://stripe.com/docs/api" rel="nofollow">stripe API</a>
|
|
</li>
|
|
<li>
|
|
<a href="https://docs.moov.io/api/" rel="nofollow">moov API</a>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="https://blog.cloudflare.com/using-apache-kafka-to-process-1-trillion-messages/"
|
|
rel="nofollow"
|
|
>Using Apache Kafka to process 1 trillion inter-service messages</a
|
|
>
|
|
</li>
|
|
</ul>
|
|
<div class="markdown-heading">
|
|
<h2 class="heading-element">Coding principle</h2>
|
|
<a
|
|
id="user-content-coding-principle"
|
|
class="anchor"
|
|
aria-label="Permalink: Coding principle"
|
|
href="#coding-principle"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>
|
|
You should know about DRY, SOLID, KISS, YAGNI, Design Pattern. The basic
|
|
is learning which is which when you read code. Truly understand will be
|
|
knowing when to use and when to not.
|
|
</p>
|
|
<p>All of these above are industry standard.</p>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">Write code that is easy delete</h3>
|
|
<a
|
|
id="user-content-write-code-that-is-easy-delete"
|
|
class="anchor"
|
|
aria-label="Permalink: Write code that is easy delete"
|
|
href="#write-code-that-is-easy-delete"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>
|
|
The way business moving is fast, so a feature is maybe implemented today,
|
|
but gets thrown out of window tomorrow (Like A/B testing, one of them is
|
|
chosen, the other says bye). So how do we adapt? The problem is to detect,
|
|
which code/function is likely stable, resisted changing and which is
|
|
likely to change.
|
|
</p>
|
|
<p>
|
|
For each service, I often split to 3 layers: handler, service, repository.
|
|
</p>
|
|
<ul>
|
|
<li>Handler layer: Handle HTTP/GRPC/Message Broker/...</li>
|
|
<li>Service layer: All rules, logic goes here.</li>
|
|
<li>
|
|
Repository layer: Interact with cache/databases using CRUD and some
|
|
cache strategy.
|
|
</li>
|
|
</ul>
|
|
<p>
|
|
Handler layer is likely never changed. Repository layer is rarely changed.
|
|
Service layer is changed daily, this is where I put so much time on.
|
|
</p>
|
|
<p>The previous question can be asked in many ways:</p>
|
|
<ul>
|
|
<li>How to move fast without breaking things?</li>
|
|
<li>How to quickly experiment new code without affecting old code?</li>
|
|
<li>...</li>
|
|
</ul>
|
|
<p>
|
|
My answer is, as Message Broker introduce concept decoupling, loosely
|
|
coupled coding. Which means, 2 functions which do not share same business
|
|
can be deleted without breaking the other.
|
|
</p>
|
|
<p>
|
|
For example, we can send noti to users using SMS, Zalo, or noti-in-app (3
|
|
providers). They are all independently feature which serves same purpose:
|
|
alert user about something. What happen if we add providers or remove
|
|
some? Existing providers keep working as usual, new providers should
|
|
behave properly too.
|
|
</p>
|
|
<p>
|
|
So we have send noti abstraction, which can be implement by each provider,
|
|
treat like a module (think like lego) which can be plug and play right
|
|
away.
|
|
</p>
|
|
<p>
|
|
And when we do not need send noti anymore, we can delete whole of it which
|
|
includes all providers and still not affecting main flow.
|
|
</p>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">Write code that is easy to test</h3>
|
|
<a
|
|
id="user-content-write-code-that-is-easy-to-test"
|
|
class="anchor"
|
|
aria-label="Permalink: Write code that is easy to test"
|
|
href="#write-code-that-is-easy-to-test"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>
|
|
Test is not a way to find bug, but to make sure what we code is actually
|
|
what we think/expect.
|
|
</p>
|
|
<p>
|
|
Best case is test with real dependencies (real servives, real Redis, real
|
|
MySQL, real Kafka, ...). But it's not easy way to setup yourself.
|
|
</p>
|
|
<p>
|
|
The easier way is to use mocks. Mock all dependencies to test all possible
|
|
edge cases you can think of.
|
|
</p>
|
|
<ul>
|
|
<li>
|
|
Unit tests is the standard (ZaloPay currently requires 90% test
|
|
coverage).
|
|
<ul>
|
|
<li>
|
|
Easy to test small to medium function which have simple rules,
|
|
likely single purpose, with table testing technique.
|
|
</li>
|
|
<li>
|
|
For big, complex function, the strategy testing goes from happy case
|
|
to each single edge case, each single if else path,... try to cover
|
|
as much as you can.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li>
|
|
Integration tests is to test your system as a whole package, can be in 2
|
|
ways:
|
|
<ul>
|
|
<li>
|
|
Locally, which require to run in your computer with fully set up
|
|
dependencies, is hard to set up.
|
|
</li>
|
|
<li>
|
|
Remotely, use DEV/... env to test full business flow with all
|
|
possible scenario.
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<p>TODO: Show example</p>
|
|
<p>How to make code easier to test. Same idea loosely coupled as above.</p>
|
|
<p>Some tips:</p>
|
|
<ul>
|
|
<li>Rely on abstraction/interface to mock</li>
|
|
<li>
|
|
Limit variable outside input (global variable, environment variable,
|
|
...)
|
|
</li>
|
|
<li>
|
|
If deleting/adding code but tests are still passed, then maybe your
|
|
tests are wrong/not enough (tests is missing some code path).
|
|
</li>
|
|
</ul>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">References</h3>
|
|
<a
|
|
id="user-content-references-1"
|
|
class="anchor"
|
|
aria-label="Permalink: References"
|
|
href="#references-1"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<ul>
|
|
<li>
|
|
<a
|
|
href="https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to"
|
|
rel="nofollow"
|
|
>Write code that is easy to delete, not easy to extend.</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="https://cerebralab.com/Imaginary_Problems_Are_the_Root_of_Bad_Software"
|
|
rel="nofollow"
|
|
>Imaginary Problems Are the Root of Bad Software</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="https://haunt98.github.io/posts-go/2022-12-25-go-test-asap.html"
|
|
rel="nofollow"
|
|
>Speed up writing Go test ASAP</a
|
|
>
|
|
</li>
|
|
</ul>
|
|
<div class="markdown-heading">
|
|
<h2 class="heading-element">System Design Concept</h2>
|
|
<a
|
|
id="user-content-system-design-concept"
|
|
class="anchor"
|
|
aria-label="Permalink: System Design Concept"
|
|
href="#system-design-concept"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<p>Start with basic: getting data from database.</p>
|
|
<div class="highlight highlight-source-mermaid">
|
|
<pre><span class="pl-k">sequenceDiagram</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">service</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">database</span>
|
|
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">database</span><span class="pl-k">:</span> <span class="pl-s">get (100ms)</span></pre>
|
|
</div>
|
|
<p>Getting data from cache first, then database later.</p>
|
|
<div class="highlight highlight-source-mermaid">
|
|
<pre><span class="pl-k">sequenceDiagram</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">service</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">cache</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">database</span>
|
|
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">cache</span><span class="pl-k">:</span> <span class="pl-s">get (5ms)</span>
|
|
<span class="pl-k">alt</span> <span class="pl-s">not exist in cache</span>
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">database</span><span class="pl-k">:</span> <span class="pl-s">get (100ms)</span>
|
|
<span class="pl-k">end</span></pre>
|
|
</div>
|
|
<p>
|
|
If data is already in cache, we can get it so fast (5ms), nearly instant.
|
|
But if not, we hit penalty, must get database after then re-update cache
|
|
if need (>105ms). The best case is worth even if hitting penalty
|
|
sometimes.
|
|
</p>
|
|
<p>Basic cache strategy: combine Write Through and Read Through</p>
|
|
<div class="highlight highlight-source-mermaid">
|
|
<pre><span class="pl-k">sequenceDiagram</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">service</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">cache</span>
|
|
<span class="pl-k">participant</span> <span class="pl-ent">database</span>
|
|
|
|
<span class="pl-k">note over </span><span class="pl-ent">service</span><span class="pl-sg">,</span><span class="pl-ent">database</span><span class="pl-k">:</span> <span class="pl-s">Read Through</span>
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">cache</span><span class="pl-k">:</span> <span class="pl-s">get</span>
|
|
<span class="pl-k">alt</span> <span class="pl-s">not exist in cache</span>
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">database</span><span class="pl-k">:</span> <span class="pl-s">get</span>
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">cache</span><span class="pl-k">:</span> <span class="pl-s">set</span>
|
|
<span class="pl-k">end</span>
|
|
|
|
<span class="pl-k">note over </span><span class="pl-ent">service</span><span class="pl-sg">,</span><span class="pl-ent">database</span><span class="pl-k">:</span> <span class="pl-s">Write Through</span>
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">database</span><span class="pl-k">:</span> <span class="pl-s">set</span>
|
|
<span class="pl-ent">service </span><span class="pl-k">->></span> <span class="pl-ent">cache</span><span class="pl-k">:</span> <span class="pl-s">set</span></pre>
|
|
</div>
|
|
<div class="markdown-heading">
|
|
<h3 class="heading-element">References</h3>
|
|
<a
|
|
id="user-content-references-2"
|
|
class="anchor"
|
|
aria-label="Permalink: References"
|
|
href="#references-2"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<ul>
|
|
<li>
|
|
<a href="https://apenwarr.ca/log/20201227" rel="nofollow"
|
|
>Systems design explains the world: volume 1</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="https://apenwarr.ca/log/20230415" rel="nofollow"
|
|
>Systems design 2: What we hope we know</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html"
|
|
rel="nofollow"
|
|
>How to do distributed locking</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="http://antirez.com/news/101" rel="nofollow"
|
|
>Is Redlock safe?</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="https://danielw.cn/cache-consistency-with-database#cache-patterns"
|
|
rel="nofollow"
|
|
>Cache Consistency with Database</a
|
|
>
|
|
</li>
|
|
</ul>
|
|
<div class="markdown-heading">
|
|
<h2 class="heading-element">Bonus</h2>
|
|
<a
|
|
id="user-content-bonus"
|
|
class="anchor"
|
|
aria-label="Permalink: Bonus"
|
|
href="#bonus"
|
|
><span aria-hidden="true" class="octicon octicon-link"></span
|
|
></a>
|
|
</div>
|
|
<ul>
|
|
<li>
|
|
<a href="https://www.jetbrains.com/idea/download/" rel="nofollow"
|
|
>IntelliJ IDEA Ultimate</a
|
|
>: Java coding
|
|
</li>
|
|
<li>
|
|
<a href="https://www.jetbrains.com/go/download/" rel="nofollow"
|
|
>GoLand</a
|
|
>: Go coding
|
|
</li>
|
|
<li>
|
|
<a href="https://www.jetbrains.com/datagrip/download/" rel="nofollow"
|
|
>DataGrip</a
|
|
>: Database GUI
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="https://redis.com/redis-enterprise/redis-insight/"
|
|
rel="nofollow"
|
|
>RedisInsight</a
|
|
>: Redis GUI
|
|
</li>
|
|
<li>
|
|
<a href="https://orbstack.dev/download" rel="nofollow">OrbStack</a>:
|
|
Better Docker Desktop
|
|
</li>
|
|
<li>
|
|
<a href="https://kreya.app/" rel="nofollow">Kreya</a>: GRPC caller
|
|
</li>
|
|
</ul>
|
|
|
|
<div>
|
|
Feel free to ask me via
|
|
<a href="mailto:hauvipapro+posts@gmail.com">email</a> or
|
|
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>.
|
|
<br />Source code is available on
|
|
<a href="https://github.com/haunt98/posts-go">GitHub</a>
|
|
<a href="https://codeberg.org/yoshie/posts-go">Codeberg</a>
|
|
<a href="https://git.sr.ht/~youngyoshie/posts-go">sourcehut</a>
|
|
<a href="https://gitea.treehouse.systems/yoshie/posts-go">Treehouse</a>
|
|
<a href="https://gitlab.com/youngyoshie/posts-go">GitLab</a>
|
|
</div>
|
|
</body>
|
|
</html>
|