scalaintermediate
Type-Safe Identifiers Pattern
Create type-safe ID wrappers to prevent mixing different entity IDs at compile time.
scalaPress ⌘/Ctrl + Shift + C to copy
import java.util.UUID
// Generic tagged type for IDs
trait IdTag
trait UserIdTag extends IdTag
trait OrderIdTag extends IdTag
trait ProductIdTag extends IdTag
opaque type Id[T <: IdTag] = String
object Id:
def apply[T <: IdTag](value: String): Id[T] = value
def generate[T <: IdTag](): Id[T] = UUID.randomUUID().toString
extension [T <: IdTag](id: Id[T])
def value: String = id
def short: String = id.take(8)
// Type aliases for convenience
type UserId = Id[UserIdTag]
type OrderId = Id[OrderIdTag]
type ProductId = Id[ProductIdTag]
object UserId:
def apply(value: String): UserId = Id[UserIdTag](value)
def generate(): UserId = Id.generate[UserIdTag]()
object OrderId:
def apply(value: String): OrderId = Id[OrderIdTag](value)
def generate(): OrderId = Id.generate[OrderIdTag]()
object ProductId:
def apply(value: String): ProductId = Id[ProductIdTag](value)
def generate(): ProductId = Id.generate[ProductIdTag]()
// Domain models using typed IDs
case class User(id: UserId, name: String, email: String)
case class Product(id: ProductId, name: String, price: Double)
case class OrderItem(productId: ProductId, quantity: Int)
case class Order(id: OrderId, userId: UserId, items: List[OrderItem])
// Repository with type-safe lookups
class UserStore:
private var users = Map.empty[UserId, User]
def save(user: User): Unit = users += (user.id -> user)
def findById(id: UserId): Option[User] = users.get(id)
// findById(orderId) // Won't compile! Type mismatch
class OrderStore:
private var orders = Map.empty[OrderId, Order]
def save(order: Order): Unit = orders += (order.id -> order)
def findById(id: OrderId): Option[Order] = orders.get(id)
def findByUser(userId: UserId): List[Order] =
orders.values.filter(_.userId == userId).toList
@main def run(): Unit =
val userId = UserId.generate()
val orderId = OrderId.generate()
val productId = ProductId.generate()
println(s"User ID: ${userId.short}...")
println(s"Order ID: ${orderId.short}...")
println(s"Product ID: ${productId.short}...")
// Create entities
val user = User(userId, "Alice", "alice@test.com")
val product = Product(productId, "Widget", 29.99)
val order = Order(orderId, userId, List(OrderItem(productId, 2)))
// Type-safe stores
val userStore = UserStore()
userStore.save(user)
println(s"Found: ${userStore.findById(userId)}")
// This would NOT compile:
// userStore.findById(orderId) // Type mismatch: OrderId vs UserId
// userStore.findById(productId) // Type mismatch: ProductId vs UserId
val orderStore = OrderStore()
orderStore.save(order)
println(s"User orders: ${orderStore.findByUser(userId).size}")Use Cases
- Preventing entity ID mixups
- Domain-driven design
- Type-safe database lookups
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
scalaadvanced
Opaque Types for Type Safety
Use Scala 3 opaque types to create zero-cost type wrappers for domain modeling.
Best for: Domain-driven type safety
#scala#opaque-types
scalaintermediate
Multiversal Equality in Scala 3
Use strict equality with CanEqual to prevent comparing unrelated types at compile time.
Best for: Preventing accidental type comparisons
#scala#equality
scalaadvanced
Phantom Types for Compile-Time Safety
Use phantom types to encode state and constraints at the type level without runtime cost.
Best for: State machine enforcement at compile time
#scala#phantom-types
scalabeginner
Scala Hello World Application
Create a basic Scala application with main method, string interpolation, and val/var basics.
Best for: Getting started with Scala
#scala#basics