Customize Guid Factory for Version 7
Describe the feature
.NET 9 introduced native support for Guid Version 7: https://learn.microsoft.com/en-us/dotnet/api/system.guid.createversion7.
The current Customizations.AddFactoryMethodForGuids customization allows for a FromNewGuid() method to be generated on the value object, which is a great convenience for value objects representing an ID. It would be great to have the option to implement this convenience method using the new version 7 Guid.
This could be in the form of a new Customization: Customizations.Addv7FactoryMethodForGuids, or perhaps a more flexible option to allow overriding the default implementation of the factory method.
A couple of workarounds are found that work, but are slighly less convenient:
- Explicitly add the factory method to each value object:
[ValueObject<Guid>]
internal readonly partial struct CustomId
{
public static CustomId FromGuidv7() => From(Guid.CreateVersion7());
}
- Enable the IVogen interface and wrap in a static factory as described in the 'Working with IDs' section: https://stevedunn.github.io/Vogen/working-with-ids.html:
[assembly: VogenDefaults(staticAbstractsGeneration: StaticAbstractsGeneration.ValueObjectsDeriveFromTheInterface | StaticAbstractsGeneration.FactoryMethods)]
public static class GuidFactory<TSelf> where TSelf : IVogen<TSelf, Guid>
{
public static TSelf NewGuid() => TSelf.From(Guid.NewGuid());
public static TSelf NewGuidv7() => TSelf.From(Guid.CreateVersion7());
}
var idv4 = GuidFactory<CustomId>.NewGuid();
var idv7 = GuidFactory<CustomId>.NewGuidv7();
Workaround # 1 provides the most convenient and familiar syntax when generating the ID, and can associate the version of the Guid used with the specific value object, but it also requires repeating the boilerplate on every value object that wants to use it, which becomes a burden when defining many value objects.
Workaround # 2 reduces the repeated boilerplate, but requires the caller to know
- The factory exists to generate the IDs
- Which version of Guid should be used for the given ID
Agree, this would be nice, but I'd extend this further if possible and include a validate check to see if the correct byte is set so that when instances are created from existing strings/guids only v7 guids are accepted. I bypassed the factory when I have Ids of this type purely because it made sense to have the logic in the type. Open to suggestions on improvements I can make with the current logic.
i.e.
/// <summary>
/// Represents a unique asset identifier.
/// </summary>
[ValueObject<Guid>]
public partial class AssetId
{
/// <summary>
/// Initializes a new instance of the <see cref="AssetId"/> class.
/// </summary>
/// <returns></returns>
public static AssetId NewAssetId() => From(Guid.CreateVersion7());
private static Validation Validate(Guid input)
{
var isValid = input != Guid.Empty && input.IsVersion7();
return isValid ? Validation.Ok : Validation.Invalid("Identifier must be a Version 7 Guid.");
}
}
public static class GuidExtensions
{
public static bool IsVersion7(this Guid guid) => (guid.ToByteArray()[7] >> 4) == 7;
}