jwojnowski/humanoid
GitHub: jwojnowski/humanoid
为 Scala 应用提供带前缀的类型安全 ID,让唯一标识符更易读且在编译期防止类型混淆。
Stars: 2 | Forks: 1
# Human(o)ID — Functional Scala 的人类友好 ID
Functional Scala 库,引入了人类友好、类型安全的 ID 前缀,旨在提升通用/全局唯一 ID 的用户体验。
灵感来源于 Stripe 的对象 ID(这是一篇关于它的[文章](https://dev.to/stripe/designing-apis-for-humans-object-ids-3o5a))。
## 快速开始
### SBT
```
libraryDependencies += "me.wojnowski" %% "humanoid" % ""
libraryDependencies += "me.wojnowski" %% "humanoid-scuid" % "" // optional, for Cuid2 integration
libraryDependencies += "me.wojnowski" %% "humanoid-uuid" % "" // optional, for UUID integration
libraryDependencies += "me.wojnowski" %% "humanoid-circe" % "" // optional, for Circe codecs
libraryDependencies += "me.wojnowski" %% "humanoid-tapir" % "" // optional, for Tapir schema/codecs
```
### 用法
#### 简单 `String` ID
让我们定义一个前缀为 "inv" 且底层 ID 类型为 `String` 的 ID:
```
type InvoiceId = PrefixedId["inv", String]
val InvoiceId = new PrefixedIdOps["inv", String]
type CustomerId = PrefixedId["cus", String]
val CustomerId = new PrefixedIdOps["cus", String]
val invoiceId1: Either[String, InvoiceId] = InvoiceId.parseRequirePrefix("inv_1234")
val invoiceId2: InvoiceId = InvoiceId.fromId("2345")
val customerId1: Either[String, CustomerId] = CustomerId.parseRequirePrefix("cus_1234").toOption.get
// val customerId2: CustomerId = invoiceId2 // ⚠️ compile error, as different prefixes mean different types
```
#### 随机 ID
对于随机 ID,可以直接生成 ID:
```
type CustomerId = PrefixedId["cus", Cuid2]
val CustomerId = new PrefixedIdOps["cus", Cuid2]
val id: F[CustomerId] = CustomerId.random[F]
```
### 支持的 ID 类型
*提示:* 不知道该选哪个随机 ID?选 `Cuid2` 吧!
| Type | Validation | Generation | Double-click-selectable | Example |
|-------------|------------|------------|-------------------------|--------------------------------------------|
| `String` | ❌ | ❌ | ❓ | `cus_mystring` |
| `UUID` | ✅ | ✅ | ❌ | `cus_550e8400-e29b-41d4-a716-446655440000` |
| `Cuid2` | ✅ | ✅ | ✅ | `cus_zwiz9glzoec3hk4ji5mgm4mp` |
| `Cuid2Long` | ✅ | ✅ | ✅ | `cus_yaajofh4u0ycvs3tbasjwoofrujvuhoq` |
如果你想使用不同的 ID 类型,请随时创建 issue!
### 定义自定义 ID 类型
要支持自定义 ID 类型(如 UUID, Cuid2 等),必须提供一个 `IdConverter` 的隐式实例:
```
implicit val myIdConverter: IdConverter[MyId] = new IdConverter[MyId] {
def renderString(id: Id): String = ???
def fromString(rawString: String): Either[String, Id] = ???
}
```
对于随机 ID,可以提供一个 `IdGenerator` 的隐式实例:
```
implicit val myIdGenerator: IdGenerator[MyId] = IdGenerator[F, MyId] {
def generate: F[MyId] = ???
}
```
### Circe 集成
```
libraryDependencies += "me.wojnowski" %% "humanoid-circe" % ""
```
```
import me.wojnowski.humanoid.circe.strict._ // require prefix when decoding, encode with prefix
import me.wojnowski.humanoid.circe.relaxed._ // don't require prefix when decoding, encode with prefix
```
### Tapir 集成
```
libraryDependencies += "me.wojnowski" %% "humanoid-tapir" % ""
```
```
import me.wojnowski.humanoid.tapir.strict._ // require prefix when decoding, encode with prefix
import me.wojnowski.humanoid.tapir.relaxed._ // don't require prefix when decoding, encode with prefix
```
标签:API设计, Circe, Cuid2, DNS解析, Human-friendly, SBT, Scala, SOC Prime, Tapir, UUID, 函数式编程, 前缀, 唯一标识符, 库, 应急响应, 开发工具, 开源项目, 数据序列化, 类型安全, 类型系统