posts-go/docs/2024-01-20-backend-thinking...

677 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.5.1/github-markdown.min.css"
/>
<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 exaplain 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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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 compability 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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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 (&gt;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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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">-&gt;&gt;</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>