Introducing memeid: An RFC-compliant library for working with UUIDs

Introducing memeid: An RFC-compliant library for working with UUIDs

We’ve recently been working on a library for generating RFC-compliant Universal Unique Identifiers (UUIDs), and we are happy to announce the first release of memeid. memeid (pronounced /mɪːˈmIːˈɪd/) is a library for generating and working with UUIDs as defined by RFC4122.

A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems.

When generated according to the standard methods, UUIDs are, for practical purposes, unique. Their uniqueness does not depend on a central registration authority or coordination between the parties generating them, unlike most other numbering schemes. – Wikipedia article on UUIDs

Why?

The standard UUID type of the JVM java.util.UUID has a number of problems, namely:

  • A bug in the comparison function that will never be fixed, which causes UUIDs in Java to be sorted differently than elsewhere https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7025832
  • Only provides UUID generation for random (V4) and non-namespaced pseudo-V3 UUIDs

In practice, the comparison bug is not that important, but we wanted a library that allowed us to create the different types of UUIDs defined by the RFC, since each of them has different semantics and performance implications.

Install

Java

Using maven

<dependency>
    <groupId>com.47deg</groupId>
    <artifactId>memeid</artifactId>
    <version>0.1.0</version>
</dependency>

Using gradle

compile group: 'com.47deg', name: 'memeid', version: '0.1.0'

Scala

Add this to your build.sbt file:

libraryDependencies += "com.47deg" %% "memeid4s" % "0.1.0"

Usage

UUID construction

Time-based (v1)

The time-based (V1) variant of UUIDs is the fastest to generate. It uses a monotonic clock and node information to generate UUIDs.

import memeid4s.UUID

UUID.V1.next
Random (v4)

The cryptographically random variant, equivalent to java.util.UUID/randomUUID.

UUID.V4.random
Namespaced (v3, v5)

Namespaced UUIDs are generated from a UUID (namespace) and a hashed value (name). V3 uses MD5 and V5 uses SHA1 hash.

val namespace = UUID.V1.next

We can now create UUIDs with the namespace and an arbitrary value as the name. It automatically works with Strings and UUIDs:

UUID.V3(namespace, "my-secret-code")

If you want to hash a custom type, you must provide an implicit memeid4s.digest.Digestible instance.

import memeid4s.digest.Digestible

case class User(firstName: String, lastName: String)

implicit val digestibleUser: Digestible[User] =
  (u: User) => u.firstName.getBytes ++ u.lastName.getBytes

The implicit instance is used to convert your type into a byte array for hashing:

UUID.V3(namespace, User("Federico", "García Lorca"))
Semi-sequential, random (SQUUID)

SQUUIDs are a non-standard variaton of V4 UUIDs that are semi-sequential. They incorporate a time-component in their 32 most significant bits to generate UUIDs that don’t fragment DB indexes.

UUID.V4.squuid

Java interoperability

memeid provides conversion method between UUID and java.util.UUID through:

val j = java.util.UUID.fromString("a5fa7934-501c-46eb-9ea7-16de3086e6d8")
val u = memeid.UUID.fromString("8b4d1529-5fd0-4a91-8f4f-ceee10d1c060")
UUID.fromUUID(j)
// res5: UUID = a5fa7934-501c-46eb-9ea7-16de3086e6d8
u.asJava
// res6: java.util.UUID = 8b4d1529-5fd0-4a91-8f4f-ceee10d1c060

Literal syntax

memeid provides literal syntax with compile-time verification for UUIDs with the uuid interpolator. To use it, add this to your build.sbt:

libraryDependencies += "com.47deg" %% "memeid4s-literal" % "0.1.0"

We can now create UUIDs with literal syntax by importing memeid.literal._

import memeid4s.literal._

uuid"cb096727-6a82-4abd-bc79-fc92be8c5d88"
// res7: UUID = cb096727-6a82-4abd-bc79-fc92be8c5d88

Invalid UUID literals will fail at compile time:

uuid"not-a-uuid"
// error: invalid UUID: not-a-uuid
// uuid"not-a-uuid"
// ^^^^^^^^^^^^^^^^

Integrations

memeid provides several modules that integrate with popular third-party libraries. If you see something missing don’t hesitate to open an issue or send a patch.

Doobie

The Doobie integration allows you to use the UUID type mapped to your database’s UUID type.

libraryDependencies += "com.47deg" %% "memeid4s-doobie" % "0.1.0"

To have the UUID mappings available in scope, you can import memeid.doobie.implicits.

import memeid4s.doobie.implicits._

def select(uuid: UUID): Query0[UUID] =
  sql"""SELECT id from test where id = ${uuid}""".query[UUID]

def insert(uuid: UUID): Update0 =
  sql"""insert into test (id) values ($uuid)""".update

val example = uuid"58d61328-1b08-1171-1ee7-1283ed639e77"
{
  for {
    _ <- insert(example).run.transact(transactor)
    u <- select(example).unique.transact(transactor)
  } yield u
}.unsafeRunSync
// res10: UUID = 58d61328-1b08-1171-1ee7-1283ed639e77
Circe
libraryDependencies += "com.47deg" %% "memeid4s-circe" % "0.1.0"

You can import memeid.circe.implicits to have the Encoder and Decoder instances for UUID in scope.

import io.circe.{ Json, Encoder, Decoder }

import memeid4s.circe.implicits._

val uuid = uuid"58d61328-1b08-1171-1ee7-1283ed639e77"
val json = Json.fromString(uuid.toString)
Encoder[UUID].apply(uuid)
// res11: Json = JString("58d61328-1b08-1171-1ee7-1283ed639e77")
Decoder[UUID].decodeJson(json)
// res12: Decoder.Result[UUID] = Right(58d61328-1b08-1171-1ee7-1283ed639e77)
Http4s
libraryDependencies += "com.47deg" %% "memeid4s-http4s" % "0.1.0"
Path parameters

Using UUID companion object, we can extract UUIDs from path parameters in URLs:

import cats.effect._

import org.http4s._
import org.http4s.dsl.io._

HttpRoutes.of[IO] {
  case GET -> Root / "user" / UUID(uuid) => Ok(s"Hello, ${uuid}!")
}
Query parameters

The http4s integrations provides implicit instances for QueryParamDecoder[UUID] and QueryParamEncoder[UUID], which you can use to derive matchers for query parameters or send UUID in request query parameters.

import cats.effect._

import org.http4s._
import org.http4s.dsl.io._

import memeid4s.http4s.implicits._

object UUIDParamDecoder extends QueryParamDecoderMatcher[UUID]("uuid")

HttpRoutes.of[IO] {
  case GET -> Root / "user" :? UUIDParamDecoder(uuid) => Ok(s"Hello, ${uuid}!")
}
Cats & Cats-effect
libraryDependencies += "com.47deg" %% "memeid4s-cats" % "0.1.0"

The cats integration provides typeclass implementation for UUID, as well as effectful constructors for UUIDs for integration with programs that use cats-effect.

Typeclasses
import cats._
import memeid4s.cats.implicits._

Order[UUID]
Hash[UUID]
Eq[UUID]
Show[UUID]
Constructors
UUID.random[IO]
UUID.v3[IO, String](namespace, "my-secret-code")
UUID.v5[IO, String](namespace, "my-secret-code")

Conclusion

memeid lets you generate and work with UUIDs as defined by RFC4122, and is implemented as a Java library that can be used from any JVM language. In this post, we took a look at its Scala API as well as integrations with some of the most popular Scala libraries, showing how you can use it to generate different types of UUIDs or encoding/decoding UUID values.

We hope you find memeid useful. Feel free to reach out if you have any questions or comments. The code is open source and available on GitHub.

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.