2

This is a question of model design, and hopefully some architecture nerds have strong opinions on this. I've been developing one that I will post in an answer.

Typically, in every codebase I've ever seen, if your model has an integer id, it is typed as an int and if it has a guid id it is a Guid. Is this really correct? Is this leaky abstraction? Is it a leaky abstraction worth keeping?

2
  • 1
    Your question could be a better one. I see where you're going with the answer, but questions need to stand on their own merit, and this one is "primarily opinion-based" (you even say so in your question). Jan 19, 2015 at 6:17
  • 1
    It is, but as the FAQ says, some subjective questions are alright so long as they're constructive. This is questioning the wisdom of a common practice which seems to me of benefit to the community at large. Jan 19, 2015 at 10:40

2 Answers 2

4

Using native types (int, Guid, etc.) instead of types which express the intent (Price, ProductIdentifier, etc.) has two drawbacks:

  1. The type change is cascaded all over the code base, making it particularly painful.

    For instance, switching from smallint (short) to uniqueidentifier (Guid) is not only painful at database level (how do you transform production data from auto-incremented fields to randomly generated UIDs?), but also at code level. You must go through the entire code base and replace short by Guid, eventually dealing with compile errors or even runtime errors (in a case of a dynamic language or when Reflection is used).

  2. The type-related methods are spread through the code base instead of being constrained within a single class. This creates a risk of code duplication.

    When you have ProductIdentifier, you can easily locate CheckIfValueInRange method: obviously, it is in ProductIdentifier class and only there.

    When you have int as product identifier, you may guess that CheckIfValueInRange is in data access layer, but then it's up to you to locate it. Under pressure, some programmers may be reluctant spending time searching for the method, and write their own checker:

    if (this.Id <= 0 || this.Id > 100000)
    {
        throw new Exception("The product ID is invalid.");
    }
    

    which may unfortunately differ from another implementation which is more conform to the spec:

    const int MAX_ID = 100000;
    if (this.Id < 0 || this.Id > MAX_ID)
    {
        throw new OutsideRangeException("The identifier is outside the allowed range.");
    }
    

    leading to refactoring/maintenance nightmare, which could easily be avoided if one were using ProductId class.

Do identifiers change their type often?

Actually, they are. A developer decides to use smallint for a small website; the website becomes hugely popular and once the limit of 32,767 values reached, the type should be changed. Another developer uses uniqueidentifier, and later it appears that there are no benefits for this type in the actual context, but having an auto-incremented, ordered value would be nice.

This makes identifiers a good candidate for using a custom type instead of a native one.

Do identifiers have business logic?

Aside range checking (see above), there are many cases where it makes sense to link the business logic to the identifier itself:

  • Generation of a new ID,

  • Checking of the existence of an ID,

  • Checking if the ID belongs to a specific group (such as ID 1 to 10000 is group A, and IDs higher than 10000 are group B),

  • etc.

Note that by removing the leaky abstraction of an ORM through the usage of a custom type, you're also creating a new leaky abstraction. Imagine you want to order something by ID. If ProductId corresponds to an auto-incremented field, it makes sense. If ProductId corresponds to a GUID, ordering doesn't make sense and you should be searching for a different criteria, like Product.CreatedTimeUtc.

3

So lately I've been thinking that semantically the id is any unique identifier; the fact that it happens to be in the integer or the guid format is an implementation detail and exposing the fact in the type is a leaky abstraction.

Thinking about it, exposing it as a string or a dedicated Identifier type has some advantages, it makes things easier to mock, repeat, or switch to ordered identifiers. Of course, the database should be whatever makes it preformant, but that is another implementation detail.

My intuition is that a dedicated Identifier type is the way to go, and if your ORM doesn't support that, a string seems a second best option.

2
  • What ORM doesn't support genuine ID fields? Seems like a non-starter to me. What do you mean by "a dedicated identifier type?" One that is automatically generated by the database? Jan 19, 2015 at 6:16
  • My understanding is that the Identifier type would sit in the application and would act as an abstraction of the DB's identifiers.
    – MetaFight
    Jan 19, 2015 at 11:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.