Skip to content

Commit 791ad7b

Browse files
authored
release 0.3.0 (#4)
update library to typeid-spec 0.3.0 ("allow '_' in type prefix") remove artifact for Java 8 (typeid-java-jdk8)
1 parent a0edd2c commit 791ad7b

File tree

33 files changed

+659
-1063
lines changed

33 files changed

+659
-1063
lines changed

.github/workflows/build-on-push.yml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@ jobs:
55
build-gradle-project:
66
runs-on: ubuntu-latest
77
steps:
8-
- uses: actions/checkout@v3
9-
- uses: actions/setup-java@v3
8+
- uses: actions/checkout@v4
9+
- uses: actions/setup-java@v4
1010
with:
1111
distribution: temurin
1212
java-version: 17
13-
- uses: gradle/gradle-build-action@v2.5.1
14-
with:
15-
gradle-version: wrapper
16-
arguments: build
13+
- uses: gradle/actions/setup-gradle@v3
14+
- run: ./gradlew build

.github/workflows/publish-on-release.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ jobs:
77
publish:
88
runs-on: ubuntu-latest
99
steps:
10-
- uses: actions/checkout@v3
11-
- uses: actions/setup-java@v3
10+
- uses: actions/checkout@v4
11+
- uses: actions/setup-java@v4
1212
with:
1313
distribution: temurin
1414
java-version: 17
15-
- uses: gradle/gradle-build-action@v2.5.1
16-
with:
17-
gradle-version: wrapper
18-
arguments: build publishAllPublicationsToOSSRHRepository
15+
- uses: gradle/actions/setup-gradle@v3
16+
- run: ./gradlew build
17+
- run: ./gradlew publishAllPublicationsToOSSRHRepository
1918
env:
2019
ORG_GRADLE_PROJECT_OSSRHUsername: ${{ secrets.OSSRH_USERNAME }}
2120
ORG_GRADLE_PROJECT_OSSRHPassword: ${{ secrets.OSSRH_TOKEN }}

README.md

Lines changed: 62 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,27 @@ Read more about TypeIDs in their [spec](https://github.com/jetpack-io/typeid).
1111

1212
## Installation
1313

14-
This library is designed to support all current LTS versions, including Java 8, whilst also making use of the features provided by the latest or upcoming Java versions. As a result, it is offered in two variants:
14+
Starting with version `0.3.0`, `typeid-java` requires at least Java 17.
1515

16-
- `typeid-java`: Requires at least Java 17. Opt for this one if the Java version is not a concern
17-
- *OR* `typeid-java-jdk8`: Supports all versions from Java 8 onwards. It handles all relevant use cases, albeit with less syntactic sugar
16+
<details>
17+
<summary>(Details on Java 8+ support)</summary>
18+
Up to version 0.2.0, 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.0`, 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.
19+
</details>
1820

1921
To install via Maven:
2022

2123
```xml
2224
<dependency>
2325
<groupId>de.fxlae</groupId>
24-
<artifactId>typeid-java</artifactId> <!-- or 'typeid-java-jdk8' -->
25-
<version>0.2.0</version>
26+
<artifactId>typeid-java</artifactId>
27+
<version>0.3.0</version>
2628
</dependency>
2729
```
2830

2931
For installation via Gradle:
3032

3133
```kotlin
32-
implementation("de.fxlae:typeid-java:0.2.0") // or ...typeid-java-jdk8:0.2.0
34+
implementation("de.fxlae:typeid-java:0.3.0")
3335
```
3436

3537
## Usage
@@ -38,6 +40,9 @@ implementation("de.fxlae:typeid-java:0.2.0") // or ...typeid-java-jdk8:0.2.0
3840

3941
### Generating new TypeIDs
4042

43+
44+
#### generate
45+
4146
To generate a new `TypeId`, based on UUIDv7 as per specification:
4247

4348
```java
@@ -47,44 +52,39 @@ typeId.prefix(); // "user"
4752
typeId.uuid(); // java.util.UUID(01890a5d-ac96-774b-bcce-b302099a8057), based on UUIDv7
4853
```
4954

50-
To construct (or reconstruct) a `TypeId` from existing arguments, which can also be used as an "extension point" to plug-in custom UUID generators:
55+
#### of
56+
57+
To construct (or reconstruct) a `TypeId` from existing arguments:
5158

5259
```java
53-
var typeId = TypeId.of("user", UUID.randomUUID()); // a TypeId based on UUIDv4
60+
var typeId = TypeId.of("user", someUuid);
5461
```
62+
As a side effect, `of` can also be used as an "extension point" to plug-in custom UUID generators.
5563
### Parsing TypeID strings
5664

5765
For parsing, the library supports both an imperative programming model and a more functional style.
66+
67+
#### parse
5868
The most straightforward way to parse the textual representation of a TypeID:
5969

6070
```java
6171
var typeId = TypeId.parse("user_01h455vb4pex5vsknk084sn02q");
6272
```
6373

64-
Invalid inputs will result in an `IllegalArgumentException`, with a message explaining the cause of the parsing failure. 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, as no stacktrace is involved at all):
74+
Invalid inputs will result in an `IllegalArgumentException`, with a message explaining the cause of the parsing failure.
75+
76+
#### parseToOptional
77+
78+
It's also possible to obtain an `Optional<TypeId>` in cases where the concrete error message is not relevant.
6579

6680
```java
67-
var maybeTypeId = TypeId.parseToOptional("user_01h455vb4pex5vsknk084sn02q");
68-
69-
// or, if you are interested in possible errors, provide handlers for success and failure
70-
var maybeTypeId = TypeId.parse("...",
71-
Optional::of, // (1) Function<TypeId, T>, called on success
72-
message -> { // (2) Function<String, T>, called on failure
73-
log.warn("Parsing failed: {}", message);
74-
return Optional.empty();
75-
});
81+
var maybeTypeId = TypeId.parseToOptional("user_01h455vb4pex5vsknk084sn02q");
7682
```
77-
**Everything shown so far works for both artifacts, `typeid-java` as well as `typeid-java-jdk8`. The following section is about features that are only available when using `typeid-java`**.
7883

79-
When using `typeid-java`:
80-
- the type `TypeId` is implemented as a Java `record`
81-
- it has an additional method that *can* be used for parsing, `TypeId.parseToValidated`, which returns a "monadic-like" structure: `Validated<T>`, or in this particular context, `Validated<TypeId>`
84+
#### parseToValidated
8285

83-
`Validated<TypeId>` can be of subtype:
84-
- `Valid<TypeId>`: encapsulates a successfully parsed `TypeId`
85-
- or otherwise `Invalid<TypeId>`: contains an error message
86+
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):
8687

87-
A simplistic method to interact with `Validated` is to manually unwrap it, analogous to `java.util.Optional.get`:
8888

8989
```java
9090
var validated = TypeId.parseToValidated("user_01h455vb4pex5vsknk084sn02q");
@@ -99,14 +99,27 @@ if(validated.isValid) {
9999
```
100100
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`.
101101

102-
A safe alternative involves methods that can be called without risk, namely:
102+
`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`):
103+
104+
```java
105+
// this compiles and runs from Java 21 onwards
106+
107+
var report = switch(TypeId.parseToValidated("...")) {
108+
case Valid(TypeId(var prefix, var uuid)) when "user".equals(prefix) -> "user with UUID" + uuid;
109+
case Valid(TypeId(var prefix, var ignored)) -> "Not a user. Prefix is " + prefix;
110+
case Invalid(var message) -> "Parsing failed :( ... " + message;
111+
};
112+
```
113+
Note the absent (and superfluous) default case. Exhaustiveness is checked during compilation!
114+
115+
Another safe alternative for working with `Validated<TypeId>` involves methods that can be called without risk, namely:
103116

104117
- For transformations: `map`, `flatMap`, `filter`, `orElse`
105118
- For implementing side effects: `ifValid` and `ifInvalid`
106119

107120
```java
108121
// transform
109-
var mappedToPrefix = TypeId.parseToValidated("user_01h455vb4pex5vsknk084sn02q");
122+
var mappedToPrefix = TypeId.parseToValidated("dog_01h455vb4pex5vsknk084sn02q")
110123
.map(TypeId::prefix) // Validated<TypeId> -> Validated<String>
111124
.filter("Not a cat! :(", prefix -> !"cat".equals(prefix)); // the predicate fails
112125

@@ -115,62 +128,46 @@ mappedToPrefix.ifValid(prefix -> log.info(prefix)) // called on success, so not
115128
mappedToPrefix.ifInvalid(message -> log.warn(message)) // logs "Not a cat! :("
116129
```
117130

118-
`Validated<T>` and its implementations `Valid<T>` and `Invalid<T>` form a sealed type hierarchy. This feature becomes especially useful in future Java versions, beginning with Java 21, which will facilitate Record Patterns (destructuring) and Pattern Matching for switch:
119-
120-
```java
121-
// this compiles and runs with oracle openjdk-21-ea+30 (preview enabled)
122131

123-
var report = switch(TypeId.parseToValidated("...")) {
124-
case Valid(TypeId(var prefix, var uuid)) when "user".equals(prefix) -> "user with UUID" + uuid;
125-
case Valid(TypeId(var prefix, _)) -> "Not a user, ignore the UUID. Prefix is " + prefix;
126-
case Invalid(var message) -> "Parsing failed :( ... " + message;
127-
}
128-
```
129-
Note the absent (and superfluous) default case. Exhaustiveness is checked during compilation!
130132

131133
## But wait, isn't this less type-safe than it could be?
132134
<details>
133135
<summary>Details</summary>
134136

135137
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.
136138

137-
Here's how a more type-safe variant could look, which I have implemented experimentally (currently not included in the artifact):
139+
Here's how more type-safe variants could look like, which I have implemented experimentally (**currently not included in the artifact**):
138140

139141
```java
140-
TypeId<User> typeId = TypeId<>.generate(USER);
141-
TypeId<User> anotherTypeId = TypeId<>.parse(USER, "user_01h455vb4pex5vsknk084sn02q");
142+
TypeId<User> typeId = TypeId.generate(USER);
143+
TypeId<User> anotherTypeId = TypeId.parse(USER, "user_01h455vb4pex5vsknk084sn02q");
142144
```
143145

144-
The downside to this approach is that each possible prefix type has to be defined manually. In particular, one must ensure that the embedded prefix name is syntactically correct:
146+
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.:
145147

146148
```java
147-
static final User USER = new User();
148-
record User() implements TypedPrefix {
149+
final class User implements TypedPrefix {
149150
@Override
150151
public String name() {
151152
return "user";
152153
}
153154
}
154-
```
155-
156-
This method would still be an improvement, as it allows `TypeId`s to be passed around in the code in a type-safe manner. However, the preferred solution would be to validate the names of the prefix types at compile time. This solution is somewhat more complex and might require, for instance, the use of an annotation processor.
157-
158-
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.
159-
</details>
160155

161-
## A word on UUIDv7
162-
<details>
163-
<summary>Details</summary>
164-
165-
TypeIDs are purposefully based on UUIDv7, one of several new UUID versions. UUIDs of version 7 begin with the current timestamp represented in the most significant bits, enabling their generation in a monotonically increasing order. This feature presents certain advantages, such as when using indexes in a database. Indexes based on B-Trees significantly benefit from monotonically ascending values.
156+
static final User USER = new User();
157+
```
166158

167-
However, the [IETF specification for the new UUID versions](https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis) is a draft yet to be finalized, meaning modifications can still be introduced, including to UUIDv7. Additionally, the specification grants certain liberties in regards to the structure of a version 7 UUID. It must always commence with a timestamp (with a minimum precision of a millisecond, but potentially more if necessary), but in the least significant bits, aside from random values, it may or may not optionally include a counter and an InstanceId.
159+
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.
168160

169-
For these reasons, this library uses a robust implementation of UUIDs for Java (as its only runtime-dependency) , specifically [java-uuid-generator (JUG)](https://github.com/cowtowncoder/java-uuid-generator). It adheres closely to the specification and, for instance, utilizes `SecureRandom` for generating random numbers, as strongly recommended by the specification (see [section 6.8](https://datatracker.ietf.org/doc/html/draft-ietf-uuidrev-rfc4122bis#section-6.8) of the sepcification).
161+
```java
162+
@TypeId(name = "UserId", prefix = "user")
163+
class MyApp {}
170164

171-
Nevertheless, as stated earlier, it is possible to use any other UUID generator implementation and/or UUID version by invoking `TypeId.of` instead of `TypeId.generate`.
165+
UserId userId = UserId.generate();
166+
UserId anotherUserId = UserId.parse("user_01h455vb4pex5vsknk084sn02q");
167+
```
172168

173-
</details>
169+
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.
170+
</details>
174171

175172
## Building From Source & Benchmarks
176173
<details>
@@ -187,12 +184,11 @@ There is a small [JMH](https://github.com/openjdk/jmh) microbenchmark included:
187184
foo@bar:~/typeid-java$ ./gradlew jmh
188185
```
189186

190-
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 64-Bit Server VM, AMD 2019gen CPU @ 3.6Ghz, 16GiB memory).
187+
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).
191188

192-
| method | op/s |
193-
|----------------------------------|----------------------:|
194-
| `TypeId.generate` + `toString` | 9.1M |
195-
| `TypeId.parse` | 9.8M |
189+
| method | op/s |
190+
|--------------------------------|------:|
191+
| `TypeId.generate` + `toString` | 10.2M |
192+
| `TypeId.parse` | 9.8M |
196193

197-
The library strives to avoid heap allocations as much as possible. The only allocations made are for return values and data from `SecureRandom`.
198194
</details>

build-conventions/build.gradle.kts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,3 @@ repositories {
66
gradlePluginPortal()
77
mavenCentral()
88
}
9-
10-
dependencies {
11-
implementation("com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:8.1.1")
12-
}

build-conventions/src/main/kotlin/de/fxlae/typeid/typeid.library-conventions.gradle.kts

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ plugins {
22
`java-library`
33
`maven-publish`
44
signing
5-
id("com.github.johnrengelman.shadow")
65
}
76

87
group = "de.fxlae"
9-
version = "0.2.0"
8+
version = "0.3.0"
109

1110
java {
1211
withJavadocJar()
@@ -30,42 +29,9 @@ tasks.javadoc {
3029
}
3130
}
3231

33-
tasks.named("jar").configure {
34-
enabled = false
35-
}
36-
37-
tasks.withType<GenerateModuleMetadata> {
38-
enabled = false
39-
}
40-
4132
val mavenArtifactId: String by project
4233
val mavenArtifactDescription: String by project
4334

44-
tasks {
45-
shadowJar {
46-
configurations = listOf(project.configurations.compileClasspath.get())
47-
include("de/fxlae/**")
48-
from(project(":lib:shared").sourceSets.main.get().output)
49-
archiveClassifier.set("")
50-
archiveBaseName.set(mavenArtifactId)
51-
}
52-
build {
53-
dependsOn(shadowJar)
54-
}
55-
}
56-
57-
val providedConfigurationName = "provided"
58-
59-
configurations {
60-
create(providedConfigurationName)
61-
}
62-
63-
sourceSets {
64-
main.get().compileClasspath += configurations.getByName(providedConfigurationName)
65-
test.get().compileClasspath += configurations.getByName(providedConfigurationName)
66-
test.get().runtimeClasspath += configurations.getByName(providedConfigurationName)
67-
}
68-
6935
publishing {
7036
publications {
7137
create<MavenPublication>("mavenJava") {

gradle/libs.versions.toml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
[versions]
2-
java-uuid-generator = "4.2.0"
3-
jackson = "2.15.2"
4-
junit = "5.9.1"
5-
assertj = "3.24.2"
6-
shadow = "8.1.1"
7-
jmh = "0.7.1"
2+
java-uuid-generator = "5.0.0"
3+
jackson = "2.17.0"
4+
junit = "5.10.2"
5+
assertj = "3.25.3"
6+
jmh = "0.7.2"
87

98
[plugins]
109
jmh = { id = "me.champeau.jmh", version.ref = "jmh" }
@@ -14,5 +13,3 @@ java-uuid-generator = { module = "com.fasterxml.uuid:java-uuid-generator", versi
1413
junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" }
1514
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }
1615
jackson-dataformat-yaml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml", version.ref = "jackson" }
17-
shadow = { module = "com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin", version.ref = "shadow" }
18-

gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@ plugins {
55
}
66

77
tasks.compileJava {
8-
options.release.set(17)
8+
options.release = 17
99
}
1010

1111
dependencies {
12-
"provided"(project(":lib:shared"))
1312
implementation(libs.java.uuid.generator)
14-
testImplementation(project(path = ":lib:shared", configuration = "testArtifacts"))
15-
jmh(project(":lib:shared"))
1613
}
1714

1815
jmh {

lib/java17/src/test/java/de/fxlae/typeid/SpecTest.java

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)