Skip to content

Commit 6e80102

Browse files
committed
microservice post
1 parent 719156f commit 6e80102

File tree

15 files changed

+935
-155
lines changed

15 files changed

+935
-155
lines changed

content/posts/microservice-apis.md

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
+++
2-
date = '2025-08-02T8:00:00+08:00'
3-
draft = true
2+
date = '2025-08-15T8:00:00+08:00'
3+
draft = false
44
title = 'Microservice with FastAPI'
55
+++
66

@@ -369,7 +369,7 @@ def pay_order(order_id: UUID):
369369

370370

371371
### Microservice Principles
372-
微服务设计原则: 如何将系统拆分为微服务 *serivce decomposition*, 以及如何估计其质量
372+
微服务设计原则: 如何将系统拆分为微服务 *service decomposition*, 以及如何估计其质量
373373
下面是三个设计原则:
374374
- Database-per-service principle 服务独立数据库原则
375375
- Loose coupling principle 松耦合原则
@@ -399,5 +399,63 @@ def pay_order(order_id: UUID):
399399
当应用于微服务设计架构时, 这意味着我们应努力围绕单一的业务能力或子域来设计服务.
400400

401401

402+
### Decomposing micorservices by business capabilities
403+
下面将 CoffeeMesh 系统根据业务内容分成以下部分
402404

405+
- 产品团队对应产品服务
406+
- 原料团队对应原料服务
407+
- 销售团队对应销售服务
408+
- 金融团队对应金融服务
409+
- 厨房团队对应厨房服务
410+
- 配送团队对应配送服务
403411

412+
在上面的微服务架构中, 将不同的业务定义为一个微服务, 这样是为了方便, 但并不一定要这样实现
413+
414+
如上的设计规则, 满足了 SRP 原则, 每个模块都处理自己的数据, 但是这种设计并不满足松耦合原则(loose couping principle), 产品服务需要确定每款产品的库存,由于库存数据在原料服务中, 这就需要依赖原料服务, 而为每个产品都设计一个面向原料服务的 API 显然不太合理.
415+
416+
因此, 这两个服务应该耦合在一起, 最终的服务结构如下:
417+
- Products service: Products and Ingredients team
418+
- Sales service: Sales team
419+
- Kitchen service: Kitchen team
420+
- Finance service: Finance team
421+
- Delivery service: Delivery service
422+
423+
424+
#### Service decomposition by subdomains
425+
通过子领域分解是一种从 领域驱动设计(domain-driven desgin, DDD) 中汲取灵感的方法.
426+
领域驱动设计是一种软件开发方法, 它专注于使用业务用户相同的语言来对业务流程和流向进行建模, 当应用于微服务设计时, DDD 能够帮助定义每个服务的核心职责和边界
427+
428+
对于 CoffeeMesh 项目, 我们希望根据下单的过程, 以及配送给客户的过程来建模, 将其分解为以下8步:
429+
1. 当用户登陆网站后, 像用户展示产品列表. 每个产品都表示是否有库存. 用户可以根据是否有库存和价格来排序
430+
2. 用户选择产品后下单
431+
3. 用户为订单付费
432+
4. 一但用户付费, 就将订单细节传递给 kitchen 服务
433+
5. kitchen 服务根据订单制作咖啡
434+
6. 用户可以查询订单进度
435+
7. 一但订单制作完成, 就安排配送
436+
8. 用户可以追踪无人机的配送进度, 直到配送到用户手中
437+
438+
根据上面步骤, 将模块划分为以下几个子领域 (subdomains)
439+
- Production Subdomain 产品子领域
440+
第一个服务用于 CoffeeMesh 产品目录的子域, 这个子域告诉用户哪些产品可用, 哪些不可用. 为此, 产品子域会追踪每种产品和原料的库存
441+
442+
- Orders Subdomain 订单子域
443+
第二步代表一个允许用户选择产品的子域, 这个子域用于管理订单的声明周期. 该子域拥有用户订单的数据, 并提供一个接口来管理订单和检查其状态. 订单领域还负责第四步的第二部分: 在成功处理付款后, 将订单传递给厨房. 同时也满足了第六步的要求: 允许用户检查其订单状态. 作为订单管理者, 订单子域还会与配送子域协作来安排配送.
444+
445+
- Payments Subdomain 支付子域
446+
第三步代表一个处理用户支付的子域. 该子域包括用户支付处理的专门逻辑, 包括银行卡验证, 与第三方支付提供商集成, 处理不同支付方式等. 支付子领域拥有与用户支付相关的数据.
447+
448+
- Kitchen Subdomain 厨房子域
449+
第五步代表一个与厨房协作来管理客户订单生产的子域. 厨房的生产系统是全自动的, 厨房子域与厨房系统进行接口交互, 以安排客户订单的生产并追踪其进度.一旦订单生产完成, 厨房子域会通知订单子域, 后者随后安排配送. 厨房子域拥有与客户订单生产相关的数据, 并公开一个接口, 允许我们向厨房发送订单并跟踪其进度. 订单子域通过与厨房子域的接口交互, 来更新订单状态, 以满足第六步的需求.
450+
451+
- Delivery Subdomain 配送子域
452+
第七步代表一个与自动化配送系统进行接口交互的子域. 该子域包含专门的逻辑, 用于解析客户的地理位置并计算到达他们的最佳路线. 它管理着配送无人机机队并优化配送, 同时拥有与所有配送相关的数据. 订单子域通过与配送子域的接口交互, 来更新客户订单的行程, 以满足第八步的需求.
453+
454+
通过以上分析, 将 CoffeeMesh 分解为5个子领域, 这些子领域可以被映射为微服务, 每个子领域都封装了定义明确, 职责清晰且拥有自己的逻辑区域. 领域驱动设计的微服务也满足了之前的微服务设计原则: 所有这些子域都可以在不依赖其他微服务的情况下执行其核心任务, 因此是松耦合的; 每个服务都拥有自己的数据, 因此符合服务独立数据库原则; 最后, 每个服务都在一个定义狭窄的子域内执行任务, 这符合单一职责原则.
455+
456+
457+
### Wrapping Up
458+
上面介绍了微服务的概念, 并通过一个 CoffeeMesh 的项目解释了如何设计一个微服务, 分别通过业务分解和通过子领域分解, 以及设计微服务的3条原则:
459+
- Database-per-service principle 数据库独享原则
460+
- Loose coupling principle 松耦合原则
461+
- Single responsibility principle 单一责任原则
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
+++
2+
date = '2025-08-02T8:00:00+08:00'
3+
draft = true
4+
title = 'Chaos Enginnering In Partice'
5+
+++
6+
###“混沌工程”实践
7+
8+
项目延期,开发说时间不够干不完
9+
老板:“不够就招人”
10+
11+
今天偶然听到旁边同事给新来的员工做code review:
12+
- Q:你这个代码不要这样写,因为......
13+
- A:哦,懂了
14+
- Q:唉?你这个 .idea/ 文件是什么?
15+
- A:啊?我也不知道
16+
- 我:不是???(一脸震惊)
17+
18+
理想:Plan Do Check Act
19+
现实:Plan Delay Cancel Apologize

content/posts/python-generics.md

Lines changed: 96 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@ title = 'Python Generics'
66
本篇文件介绍 Python 中的 泛型(Generics)
77

88
### Intro
9-
在没有泛型的情况下, 会遇上一下几个问题:
9+
在没有泛型的情况下, 会遇上以下几个问题:
1010

11-
1. 难以表达意图
11+
1. 难以表达意图
1212
假设你编写了一个函数, 它接受一个列表, 并返回列表中的第一个元素.
1313
在不使用类型提示的情况下, 这个函数可以处理任何类型的列表, 但我们无法在函数签名中表达"返回的元素的类型与列表中的元素类型相同"这个意图
14-
```Python
14+
```python
1515
def get_first_element(items):
1616
return items[0]
1717
```
1818

19-
2. 丧失类型信息
19+
2. 丧失类型信息
2020
如果使用类型提示, 可能会像下面这样写, 但这样会丢失类型信息.
21-
`list[Any]` 表示可以接收任何类型的列表, 但 `-> Any` 意味着不知道返回的元素类型是什么, 这使得 mypy 这里静态类型检测工具无法追踪类型, 降低了代码的可读性和安全性
22-
```Python
21+
`list[Any]` 表示可以接收任何类型的列表, 但 `-> Any` 意味着不知道返回的元素类型是什么, 这使得 mypy 等静态类型检测工具无法追踪类型, 降低了代码的可读性和安全性
22+
```python
2323
from typing import Any
2424
def get_first_element(items: list[Any]) -> Any:
2525
return items[0]
@@ -28,38 +28,41 @@ title = 'Python Generics'
2828
first_str = get_first_element(["hello", "world"])
2929
```
3030

31-
3. 代码重复
31+
3. 代码重复
3232
如果为每种可能的类型都编写一个单独的函数, 则会导致代码重复
33-
```Python
34-
def get_first_int(items: List[int]) -> int:
33+
```python
34+
def get_first_int(items: list[int]) -> int:
3535
return items[0]
3636

37-
def get_first_str(items: List[str]) -> str:
37+
def get_first_str(items: list[str]) -> str:
3838
return items[0]
3939
```
4040

4141
通过引入 **类型变量** (TypeVar) 来解决问题, 类型变量就像一个占位符, 代表在未来某时刻会被具体指定的类型
42-
```Python
42+
```python
4343
from typing import TypeVar
4444

4545
T = TypeVar("T")
4646

4747
def get_first_element(items: list[T]) -> T:
48-
reuturn items[0]
48+
return items[0]
4949

5050
# 现在, 类型检查工具可以正确推断出类型
5151
first_str: str = get_first_element(["hello", "world"])
5252
first_int: int = get_first_element([1, 2, 3])
5353
```
54-
- `T = TypeVar('T')` 定义了一个名为 T 的类型变量, 这里 T 只是一个约定熟成的名字, 也可以使用其他字母
54+
55+
- `T = TypeVar('T')` 定义了一个名为 T 的类型变量, 这里 T 只是一个约定俗成的名字, 也可以使用其他字母
5556
- `items: list[T]` 表示 `items` 是一个列表, 其内部元素类型是 T
5657
- `-> T`: 返回类型也是 T
5758
- 当使用 `["hello", "world"]` 调用函数时, 静态类型检查器会推断出 T 是 str, 返回类型为 str
5859
- 当使用 `[1, 2, 3]` 调用函数时, T 被推断为 int
5960

61+
**注意**: 这个函数假设列表非空, 如果传入空列表会抛出 `IndexError`
62+
6063
### Generic Class
6164
除了函数, 泛型也常用于定义泛型类
62-
```Python
65+
```python
6366
from typing import TypeVar, Generic
6467

6568
T = TypeVar("T")
@@ -76,97 +79,138 @@ class Box(Generic[T]):
7679

7780
# 创建一个存储字符串的 Box
7881
string_box = Box(["apple", "banana"])
79-
item_str = string_box.get() # str
82+
item_str = string_box.get() # str
8083
string_box.add("cherry")
8184

8285
# 创建一个存储整数的 Box
8386
int_box = Box([10, 20])
84-
item_int = int_box.get() # int
87+
item_int = int_box.get() # int
8588
int_box.add(30)
8689
```
87-
- `TypeVar` 定义类型参数: 相当于一个占位符, 将来由使用者指定具体类型
90+
- `TypeVar` 定义类型变量: 相当于一个占位符, 将来由使用者指定具体类型
8891
- `Generic` 定义泛型类或泛型接口: 使这个类在类型检查器眼中变成一个模板
8992

90-
91-
### Advanced Useage
93+
### Advanced Usage
9294
简单介绍一下泛型的一些进阶用法
9395

94-
- 多类型参数
95-
```Python
96+
- **多类型参数**
97+
```python
98+
from typing import TypeVar, Generic
99+
96100
K = TypeVar("K")
97101
V = TypeVar("V")
98102

99103
class Pair(Generic[K, V]):
100104
def __init__(self, key: K, value: V):
101105
self.key = key
102106
self.value = value
107+
108+
def get_key(self) -> K:
109+
return self.key
110+
111+
def get_value(self) -> V:
112+
return self.value
113+
114+
# 使用示例
115+
pair = Pair("name", 25) # Pair[str, int]
103116
```
104-
支持多个类型参数, 类似 `dict[K, V]` 的结构
117+
支持多个类型变量, 类似 `dict[K, V]` 的结构
105118

106-
- Constraints 类型约束
107-
有时候可能希望泛型只能是某些类型
108-
```Python
119+
- **类型约束 (Constraints)**
120+
有时候可能希望泛型只能是某些特定类型
121+
```python
109122
from typing import TypeVar
110123

111124
Number = TypeVar('Number', int, float)
112125

113126
def add(a: Number, b: Number) -> Number:
114127
return a + b
128+
129+
# 正确使用
130+
result1 = add(1, 2) # int
131+
result2 = add(1.5, 2.3) # float
132+
133+
# 错误使用: mypy 会报错
134+
# result3 = add("hello", "world") # str 不被允许
115135
```
116136
`Number` 只能为 intfloat, 传入其他类型, 类型检查工具会报错
117137

118-
- Convariant / Contravariant 协变于逆变
119-
在泛型类型中, 可以控制类型参数的变型关系
120-
```Python
138+
- **协变与逆变 (Covariance/Contravariance)**
139+
在泛型类型中, 可以控制类型变量的变型关系
140+
```python
121141
from typing import Generic, TypeVar
122142

123-
T_co = TypeVar("T_co", convariant=True) # 协变
124-
T_contra = TypeVar("T_contra", contravariant=True) # 逆变
143+
T_co = TypeVar("T_co", covariant=True) # 协变
144+
T_contra = TypeVar("T_contra", contravariant=True) # 逆变
125145

126-
class ReadOnlyBox(Generic[T_co]):
146+
class Producer(Generic[T_co]):
147+
"""只产出 T_co 类型的数据 (协变)"""
127148
def __init__(self, value: T_co):
128-
self.value = value
129-
130-
class Writer(Generic[T_contra]):
131-
def __init__(self, value, T_contra):
132-
...
149+
self._value = value
150+
151+
def get(self) -> T_co:
152+
return self._value
153+
154+
class Consumer(Generic[T_contra]):
155+
"""只消费 T_contra 类型的数据 (逆变)"""
156+
def __init__(self):
157+
pass
158+
159+
def consume(self, value: T_contra) -> None:
160+
print(f"Consuming: {value}")
133161
```
134-
协变: 面向产出(只读), 允许子类替代父类
135-
逆变: 面向消费(只写), 允许父类替代子类
136-
主要用于接口设计中(读/写分离)
162+
- **协变 (covariant)**: 如果 A 是 B 的子类型, 那么 `Generic[A]` 也是 `Generic[B]` 的子类型. 适用于只产出数据的场景
163+
- **逆变 (contravariant)**: 如果 A 是 B 的子类型, 那么 `Generic[B]``Generic[A]` 的子类型. 适用于只消费数据的场景
164+
- 这主要用于接口设计中的读/写分离
137165

138-
- 泛型与 Protocol
166+
- **泛型与 Protocol**
139167
`Protocol` 允许定义泛型接口 (duck typing)
140-
```Python
141-
from typing import Protocol
168+
```python
169+
from typing import Protocol, TypeVar
170+
171+
T = TypeVar('T')
142172

143173
class SupportsLen(Protocol):
144174
def __len__(self) -> int: ...
145175

146176
def total_length(items: list[SupportsLen]) -> int:
147177
return sum(len(x) for x in items)
178+
179+
# 使用示例
180+
result = total_length(["hello", [1, 2, 3], {"a": 1}]) # 可以接受任何有 __len__ 方法的对象
148181
```
149-
任何实现了`__len__`方法的对象都能接受, 比继承更加灵活
182+
任何实现了`__len__`方法的对象都能被接受, 比继承更加灵活
150183

151-
- 泛型在标准库中的使用
152-
- 集和类: `list[T]`, `dict[K, V]`, `set[T]`
184+
- **泛型在标准库中的使用**
185+
- 集合类: `list[T]`, `dict[K, V]`, `set[T]`
153186
- 迭代器: `Iterator[T]`, `Iterable[T]`
154187
- 函数工具: `Callable[[T1, T2], R]`
155188
- 上下文管理器: `ContextManager[T]`
156189

157-
```Python
190+
```python
158191
from typing import Callable
159192

160-
F = Callable[[int, int], int]
161-
162-
def operate(a: int, b: int, func: F) -> int:
193+
def operate(a: int, b: int, func: Callable[[int, int], int]) -> int:
163194
return func(a, b)
195+
196+
# 使用示例
197+
def add(x: int, y: int) -> int:
198+
return x + y
199+
200+
def multiply(x: int, y: int) -> int:
201+
return x * y
202+
203+
result1 = operate(5, 3, add) # 8
204+
result2 = operate(5, 3, multiply) # 15
164205
```
165206

166-
167207
### Wrapping Up
168208
泛型是 Python 类型提示系统中一个非常强大的工具, 它通过类型变量帮助我们编写更加灵活、安全且可维护的代码.
169209

170-
它虽然不会影响程序的运行时行为, 但它为静态类型分析提供了必要的信息, 使得代码意图更加清晰, 并且能在早期发现类型错误.
210+
它虽然不会影响程序的运行时行为 (类型信息在运行时会被擦除), 但它为静态类型分析提供了必要的信息, 使得代码意图更加清晰, 并且能在早期发现类型错误.
171211

172-
Python 的泛型是类型提示系统的一部分, 和 C++/Java 的编译期泛型不同, 它的作用主要是: 帮助 IDE 和类型检查工具发现类型错误, 提升代码可读性和可维护性, 提供更精确的 API 类型签名等
212+
Python 的泛型是类型提示系统的一部分, 和 C++/Java 的编译期泛型不同, 它的作用主要是:
213+
- 帮助 IDE 和类型检查工具发现类型错误
214+
- 提升代码可读性和可维护性
215+
- 提供更精确的 API 类型签名
216+
- 支持更好的代码重用和抽象

0 commit comments

Comments
 (0)