Giter VIP home page Giter VIP logo

typeid-java's Introduction

typeid-java

Build Status Maven Central License Info

A Java implementation of TypeID.

TypeIDs are a modern, type-safe, globally unique identifier based on the upcoming UUIDv7 standard. They provide a ton of nice properties that make them a great choice as the primary identifiers for your data in a database, APIs, and distributed systems. Read more about TypeIDs in their spec.

Installation

Starting with version 0.3.0, typeid-java requires at least Java 17.

(Details on Java 8+ support) Up to version 0.2.x, a separate artifact called `typeid-java-jdk8` was published, supporting Java versions 8 and higher, and covering all relevant use cases, albeit with less syntactic sugar. If you are running Java 8 through 16, you can still use `typeid-java-jdk8:0.2.x`, which is still available and remains fully functional. However, it will no longer receive updates and is limited to the TypeId spec version 0.2.0.

To install via Maven:

<dependency>
    <groupId>de.fxlae</groupId>
    <artifactId>typeid-java</artifactId>
    <version>0.3.1</version>
</dependency>

For installation via Gradle:

implementation("de.fxlae:typeid-java:0.3.1")

Usage

TypeId instances can be obtained in several ways. They are immutable and thread-safe.

Generating new TypeIDs

generate

To generate a new TypeId, based on UUIDv7 as per specification:

var typeId = TypeId.generate("user");
typeId.toString(); // "user_01h455vb4pex5vsknk084sn02q"
typeId.prefix(); // "user"
typeId.uuid(); // java.util.UUID(01890a5d-ac96-774b-bcce-b302099a8057), based on UUIDv7

of

To construct (or reconstruct) a TypeId from existing arguments:

var typeId = TypeId.of("user", someUuid);

As a side effect, of can also be used as an "extension point" to plug-in custom UUID generators.

Parsing TypeID strings

For parsing, the library supports both an imperative programming model and a more functional style.

parse

The most straightforward way to parse the textual representation of a TypeID:

var typeId = TypeId.parse("user_01h455vb4pex5vsknk084sn02q");

Invalid inputs will result in an IllegalArgumentException, with a message explaining the cause of the parsing failure.

parseToOptional

It's also possible to obtain an Optional<TypeId> in cases where the concrete error message is not relevant.

var maybeTypeId = TypeId.parseToOptional("user_01h455vb4pex5vsknk084sn02q");

parseToValidated

If you prefer working with errors modeled as return values rather than exceptions, this is also possible (and is much more performant for untrusted input with high error rates, as no stacktrace is involved):

var validated = TypeId.parseToValidated("user_01h455vb4pex5vsknk084sn02q");

if(validated.isValid) {
    var typeId = validated.get();
    // Proceed with typeId
} else {
    var message = validated.message();
    // Optionally, do something with the error message (or omit this branch completely)
}

Note: Checking validated.isValid is advisable for untrusted input. Similar to Optional.get, invoking Validated.get for invalid TypeIds (or Validated.message for valid TypeIds) will lead to a NoSuchElementException.

Validated and its implementations Valid and Invalid form a sealed type hierarchy. This feature becomes especially useful in more recent Java versions, beginning with Java 21, which facilitates Record Patterns (destructuring) and Pattern Matching for switch (yes, TypeId is a record):

// this compiles and runs from Java 21 onwards

var report = switch(TypeId.parseToValidated("...")) {
    case Valid(TypeId(var prefix, var uuid)) when "user".equals(prefix) -> "user with UUID" + uuid;
    case Valid(TypeId(var prefix, var ignored)) -> "Not a user. Prefix is " + prefix;
    case Invalid(var message) -> "Parsing failed :( ... " + message;
};

Note the absent (and superfluous) default case. Exhaustiveness is checked during compilation!

Another safe alternative for working with Validated<TypeId> involves methods that can be called without risk, namely:

  • For transformations: map, flatMap, filter, orElse
  • For implementing side effects: ifValid and ifInvalid
// transform
var mappedToPrefix = TypeId.parseToValidated("dog_01h455vb4pex5vsknk084sn02q")
    .map(TypeId::prefix)  // Validated<TypeId> -> Validated<String>
    .filter("Not a cat! :(", prefix -> !"cat".equals(prefix)); // the predicate fails

// execute side effects, e.g. logging
mappedToPrefix.ifValid(prefix -> log.info(prefix)) // called on success, so not in this case
mappedToPrefix.ifInvalid(message -> log.warn(message)) // logs "Not a cat! :("

But wait, isn't this less type-safe than it could be?

Details

That's correct. The prefix of a TypeId is currently just a simple String. If you want to validate the prefix against a specific "type" of prefix, this subtly means you'll have to perform a string comparison.

Here's how more type-safe variants could look like, which I have implemented experimentally (currently not included in the artifact):

TypeId<User> typeId = TypeId.generate(USER);
TypeId<User> anotherTypeId = TypeId.parse(USER, "user_01h455vb4pex5vsknk084sn02q");

The downside to this approach is that each possible prefix has to be defined manually as its own type that contains the prefix' string representation, e.g.:

final class User implements TypedPrefix {
    @Override
    public String name() {
        return "user";
    }
}

static final User USER = new User();

Another solution is to validate the names of the prefix types at compile time. This solution is somewhat more complex as it requires an annotation processor.

@TypeId(name = "UserId", prefix = "user")
class MyApp {}

UserId userId = UserId.generate();
UserId anotherUserId = UserId.parse("user_01h455vb4pex5vsknk084sn02q");

If I find the motivation, I will complete the experimental version and integrate it as a separate variant into its own package (e.g., ..typed), which can be used alternatively.

Building From Source & Benchmarks

Details
foo@bar:~$ git clone https://github.com/fxlae/typeid-java.git
foo@bar:~$ cd typeid-java
foo@bar:~/typeid-java$ ./gradlew build

There is a small JMH microbenchmark included:

foo@bar:~/typeid-java$ ./gradlew jmh

In a single-threaded run, all operations perform in the range of millions of calls per second, which should be sufficient for most use cases (used setup: Eclipse Temurin 17 OpenJDK Server VM, 2021 AMD mid-range notebook CPU).

method op/s
TypeId.generate + toString 10.2M
TypeId.parse 9.8M

typeid-java's People

Contributors

aleris avatar fxlae avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

typeid-java's Issues

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.