Code Monkey home page Code Monkey logo

strictid's Introduction

StrictId

Strongly-typed, ergonomic, compatible, fun to use identifiers for your entities

Get on NuGet:
StrictId EFCore HotChocolate


What

public class Person {
    public Id<Person> Id { get; init; } // Strongly typed ID, lexicographically sortable, and round-trip convertible to Guid, Ulid, and string
    public Id<Dog> BestFriendId { get; set; } // No confusion about what ID we are looking for here
    public List<Id> Friends { get; set; } // Non-strict/non-generic version also included
}
  • Strongly-typed IDs for your entities, or anything else
  • Ulid as the underlying value, which can easily be converted to and from Guid, string, or byte arrays
  • Ergonomic, developer-friendly usage without ceremony, boilerplate, or annoyance
  • Cure to primitive obsession by being a DDD-friendly value object
  • Built-in JSON conversion support for System.Text.Json
  • Plug-and-play support for Entity Framework Core incl. value converters and value generators, with StrictId.EFCore
  • Plug-and-play support for HotChocolate GraphQL incl. custom scalars for Id<T> and Id, with StrictId.HotChocolate
  • Easy to create your own integrations and converters thanks to lack of magic
  • Tiny memory footprint and highly efficient

How

Recommended, but optional
In your global usings file, add the following to save yourself a few keystrokes:

global using StrictId;

Create

Id<Person>.NewId(); // Generate a new random ID
new Id<Person>("01HV9AF3QA4T121HCZ873M0BKK"); // Create from ULID string
new Id<Person>("018ED2A7-8EEA-2682-20C5-9F41C7402E73"); // Create from GUID string
new Id<Person>(Ulid.NewUlid()); // Create from ULID
new Id<Person>(Guid.NewGuid()); // Create from GUID
new Id<Person>(Id.NewId()); // Create from non-typed ID

Id<Person> id = Ulid.NewUlid(); // Convert implicitly from Ulid
Id<Person> id = Guid.NewGuid(); // Convert implicitly from Guid
Id<Person> id = Id.NewId(); // Convert implicitly from non-typed Id
var id = (Id<Person>)"01HV9AF3QA4T121HCZ873M0BKK"; // Cast from string

Id<Person> id = Id<Person>.Parse("018ED2A7-8EEA-2682-20C5-9F41C7402E73"); // Parse from Guid or Ulid
bool success = Id<Person>.TryParse("01HV9AF3QA4T121HCZ873M0BKK", out Id<Person> id); // Safely parse from Guid or Ulid

Usage of the non-typed Id is identical.

Convert

var id = new Id<Person>("01HV9AF3QA4T121HCZ873M0BKK");

id.ToString(); // "01HV9AF3QA4T121HCZ873M0BKK"
id.ToUlid(); // Same as Ulid.Parse("01HV9AF3QA4T121HCZ873M0BKK");
id.ToGuid(); // Same as Guid.Parse("018ED2A7-8EEA-2682-20C5-9F41C7402E73");
id.ToByteArray(); // byte[]
id.ToId() // Id("018ED2A7-8EEA-2682-20C5-9F41C7402E73")

Benefit

StrictId will prevent you from accidentally doing bad things, and lets you do nice things instead:

var personId = Id<Person>.NewId();
var dogId = Id<Dog>.NewId();

if (personId == dogId) Console.Write("Uh oh"); // Compiler error

public void Feed(Id<Dog> id) { 
    GetDog(id).FeedLeftovers();
}

Feed(personId); // Compiler error

// But:
public class Diet {
    public void Feed(Id<Dog> id) { 
        GetDog(id).FeedLeftovers();
    }
    
    public void Feed(Id<Person> id) { 
        GetPerson(id).FeedMichelinStarMeal();
    }
}

Feed(personId); // We eat well tonight. Better method overloads!

With Entity Framework Core

Install StrictId.EFCore via NuGet

In your DbContext:

using StrictId.EFCore;

public class MyDatabase (DbContextOptions<MyDatabase> options) : DbContext(options)
{
    protected override void ConfigureConventions (ModelConfigurationBuilder builder)
    {
        // ...
        
        builder.ConfigureStrictId();
    }
}

To generate values:

using StrictId.EFCore;

// ...

builder.Property(e => e.Id)
    .ValueGeneratedOnAdd()
    .HasStrictIdValueGenerator();

Notes

Id values are stored as fixed-length Ulid strings in the database (e.g. "01HV9AF3QA4T121HCZ873M0BKK"). An alternative value converter for storing them as Guid is also included (StrictId.EFCore.ValueConverters.IdToGuidConverter). Keep in mind that storing the IDs as Guid makes the database representation visually different from the normal string representation, which can be inconvenient. If you would prefer to store IDs as byte arrays, you can create your own value generator and converter based on the ones included. Keep in mind, though, that the small improvement you gain in database performance and storage by using byte arrays is most likely not worth the loss of readability and clarity.

With Hot Chocolate GraphQL

Install StrictId.HotChocolate via NuGet

On the request executor builder, configure strict IDs:

builder.Services.AddGraphQLServer()
    // ...
    .AddStrictId();

Scalars will be created for each strict ID, named {Type}Id. For example, Id<Person> would become PersonId in the GraphQL schema.

Why

  • Using primitives such as Guid or Ulid as the type for IDs can easily lead to mixing up method arguments and assignments
  • Using value objects makes your code easier to read and more DDD-friendly (see primitive obsession)
  • Other similar packages are cumbersome, non-compatible, and full of magic™, while StrictId's Id is just a simple generic type, no source generation or other hocus-pocus needed
  • Ulid as the underlying type provides neat benefits over simple Guids, as they are ordered, making databases less fragmented, and look nicer as strings

Acknowledgements

  • Ulid - Library for ULID in C#, used for much of the underlying functionality
  • StronglyTypedId - For doing this first, but in a much more convoluted, non-ergonomic way

License

MIT

strictid's People

Contributors

lippur avatar meigs2 avatar

Stargazers

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

Watchers

 avatar

Forkers

meigs2 josea

strictid's Issues

String implicit/explicit conversion issues/stance

Hi!

I'm loving the library. However there's one slight issue with the current auto-conversions between Id <-> string.

First, it seems that some conversions are implicit, while others are explicit, for example between Id<T> and Id, string -> Id is implicit, while it is explicit for string -> Id<T>. Seems the API isn't consistent. This caused some EF Core queries I was trying to make to fail, as the query was actually downcasting the ID column of my database to a string, and the string representation of ID in Sql Server is not the same as Ulid's Base32 representation, causing the query to fail.

Is there a reason certain casts are implicit vs explicit? If not, I think the best approach is to make all upcasts to Id and Id<T> implicit, and all downcasts explicit. This way you can't get into the scenario I was in where I thought a comparison was upcasting to Id and not downcasting Id. I feel like if you're ever using this library you're really wanting to operate only on the Id types, and if you want to do so then you can easily call ToString() to ToId().

Thoughts?

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.