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, 函数式编程, 前缀, 唯一标识符, 库, 应急响应, 开发工具, 开源项目, 数据序列化, 类型安全, 类型系统