Strongly typed identifier explained

A strongly typed identifier is user-defined data type which serves as an identifier or key that is strongly typed. This is a solution to the "primitive obsession" code smell as mentioned by Martin Fowler. The data type should preferably be immutable if possible. It is common for implementations to handle equality testing, serialization and model binding.

The strongly typed identifier commonly wraps the data type used as the primary key in the database, such as a string, an integer or universally unique identifier (UUID).

Web frameworks can often be configured to model bind properties on view models that are strongly typed identifiers. Object–relational mappers can often be configured with value converters to map data between the properties on a model using strongly typed identifier data types and database columns.

Examples

C#

C# have records which provide immutability and equality testing.[1] The record is sealed to prevent inheritance.[2] It overrides the built-in ToString method.[3]

This example implementation includes a static method which can be used to initialize a new instance with a randomly generated globally unique identifier (GUID).

///

/// Represents a user identifier./// /// The user identifier.public sealed record UserId(Guid Id)

C++

C++ have structs but not immutability so here the id field is marked as private with a method named value to get the value.

struct UserId ;

ostream& operator << (ostream &os, const UserId &id)

Crystal

Crystal's standard library provides the record macro for creating records which are immutable structs and lets you create override the built-in to_s method.[4] require "uuid"

  1. Represents a user identifier.

record UserId, id : String do def initialize @id = UUID.v4.to_s end

def to_s(io) io << id end

def self.empty self.new(UUID.empty.to_s) endend

D

D have immutable structs.[5]

import std;

/** Represents a user identifier. */immutable struct UserId

Dart

Dart have classes with operator overloading.import 'package:meta/meta.dart';

/// Represents a user identifier.@immutablefinal class UserId

F#

F# lets you create override the Equals, GetHashCode and ToString methods.open System

///

/// Represents a user identifier./// /// The user identifier.type UserId(id: Guid) = member x.id = id static member New = Guid.NewGuid static member Empty = Guid.Empty override x.Equals(b) = match b with | :? UserId as p -> id = p.id | _ -> false override x.GetHashCode = hash id override x.ToString = id.ToString

Go

Go have structs which provide equality testing. Go however does not provide immutability.// Represents a user identifier.type UserId struct

// Creates a new user identifier.func NewUserId(id string) UserId

func (x UserId) String string

Groovy

Groovy have record classes which provide immutability and equality testing.[6] /** * Represents a user identifier. * * @param id The user identifier. */record Message(String id)

Haskell

Haskell can create user-defined custom data types using the newtype keyword.[7] It provides equality testing using the Eq standard class and printing using the Read and Show standard classes.-- Represents a user identifier.newtype UserId = UserId String deriving (Eq, Read, Show)

Java

Java have records which provide equality testing.[8] The record is declared using the final modifier keyword to prevent inheritance. It overrides the built-in toString method.import java.util.UUID;

/** * Represents a user identifier. * @param id The user identifier. */public final record UserId(UUID id)

JavaScript

This JavaScript example implementation provides the toJSON method used by the JSON.stringify[9] function to serialize the class into a simple string instead of a composite data type.It calls Object.freeze to make the instance immutable.[10] It overrides the built-in toString method[11] and the valueOf method.[12] class UserId

Julia

Julia have immutable composite data types.[13] using UUIDs

"Represents a user identifier."struct UserId id::UUIDend

Base.string(userId::UserId) = userId.id

Kotlin

Kotlin have "inline classes".[14] /** * Represents a user identifier. * * @property id The user identifier. * @constructor Creates a user identifier. */@JvmInlinepublic value class UserId(public val id: String)

Nim

Nim have "distinct types".[15] [16]

    1. Represents a user identifier.

type UserId* = distinct string

PHP

This PHP example implementation implements the __toString magic method.[17] Furthermore, it implements the JsonSerializable interface which is used by the built-in json_encode function to serialize the class into a simple string instead of a composite data type.[18] The class is declared using the final modifier keyword to prevent inheritance.[19] PHP has traits as a way to re-use code.[20] /** * Represents a user identifier. */final class UserId implements JsonSerializable

/** * Provides methods for use with strongly typed identifiers. */trait StronglyTypedIdentifier

Python

Python has data classes which provides equality testing and can be made immutable using the frozen parameter.[21] It overrides the __str__ dunder method.[22]

This example implementation includes a static method which can be used to initialize a new instance with a randomly generated universally unique identifier (UUID).

from dataclasses import dataclassimport uuid

@dataclass(frozen=True)class UserId: """Represents a user identifier."""

id: uuid.UUID

@staticmethod def new -> Self: """Create a new user identifier.""" return __class__(uuid.uuid4)

def __str__(self): return str(self.id)

Python also has NewType which can be used to create new data types.[23] from typing import NewType

UserId = NewType('UserId', int)

Ruby

Ruby have data classes which provides equality testing and are immutable.[24] It overrides the built-in to_s method.

This example implementation includes a static method which can be used to initialize a new instance with a randomly generated universally unique identifier (UUID).

require 'securerandom'

  1. Represents a user identifier.

UserId = Data.define(:id) do # Create a new user identifier. def self.create self.new(SecureRandom.uuid) end

def self.empty self.new('00000000-0000-0000-0000-000000000000') end

def to_s id endend

Rust

In Rust this can be done using a tuple struct containing a single value.[25] This example implementation implements the Debug[26] and the PartialEq[27] traits. The PartialEq trait provides equality testing.// Represents a user identifier.

  1. [derive(Debug, PartialEq)]

pub struct UserId(String);

Scala

Scala have case classes which provide immutability and equality testing.[28] The case class is sealed to prevent inheritance.import java.util.UUID

/** Represents a user identifier. * * @constructor * Create a new user identifier. * @param id * The user identifier. */sealed case class UserId(id: UUID)

object UserId: /** Initializes a new instance of the UserId class. */ def create: UserId = UserId(UUID.randomUUID)

Swift

Swift have the CustomStringConvertible protocol which can be used to provide its own representation to be used when converting an instance to a string,[29] and the Equatable protocol which provides equality testing.[30] import Foundation

/// Represents a user identifier.struct UserId: CustomStringConvertible, Equatable

See also

External links

Notes and References

  1. Web site: Records - C# reference . learn.microsoft.com . 23 January 2023 . en-us.
  2. Web site: sealed modifier - C# Reference . learn.microsoft.com . 23 January 2023 . en-us.
  3. Web site: Object.ToString Method (System) . learn.microsoft.com . 14 June 2023 . en-us.
  4. Web site: Structs - Crystal . crystal-lang.org . 21 February 2024.
  5. Web site: Structs, Unions - D Programming Language . dlang.org . 30 May 2023.
  6. Web site: The Apache Groovy programming language - Object orientation . groovy-lang.org . 24 December 2023.
  7. Web site: Newtype - HaskellWiki . wiki.haskell.org . 18 June 2023.
  8. Web site: Record Classes . Oracle Help Center . 24 January 2023.
  9. Web site: JSON.stringify - JavaScript MDN . developer.mozilla.org . 23 January 2023.
  10. Web site: Object.freeze - JavaScript MDN . developer.mozilla.org . 23 January 2023.
  11. Web site: Object.prototype.toString - JavaScript MDN . developer.mozilla.org . 23 January 2023.
  12. Web site: Object.prototype.valueOf - JavaScript MDN . developer.mozilla.org . 23 January 2023.
  13. Web site: Types · The Julia Language . docs.julialang.org . 30 May 2023.
  14. Web site: Inline classes Kotlin . Kotlin Help . 23 January 2023.
  15. Web site: Nim Manual . nim-lang.org . 4 August 2023.
  16. Web site: Nim by Example - Distinct Types . nim-by-example.github.io . 4 August 2023.
  17. Web site: PHP: Magic Methods - Manual . www.php.net . 23 January 2023.
  18. Web site: PHP: JsonSerializable::jsonSerialize - Manual . www.php.net . 23 January 2023.
  19. Web site: PHP: Final Keyword - Manual . www.php.net . 23 January 2023.
  20. Web site: PHP: Traits - Manual . www.php.net . 2 May 2023.
  21. Web site: dataclasses — Data Classes . Python documentation . . 23 January 2023.
  22. Web site: 3. Data model . Python documentation . . 12 June 2023.
  23. Web site: typing — Support for type hints . Python documentation . . 17 June 2023.
  24. Web site: class Data - Documentation for Ruby 3.3 . docs.ruby-lang.org . 6 February 2023.
  25. Web site: New Type Idiom - Rust By Example . doc.rust-lang.org . 18 June 2023.
  26. Web site: Debug in std::fmt - Rust . doc.rust-lang.org . 23 January 2023.
  27. Web site: PartialEq in std::cmp - Rust . doc.rust-lang.org . 23 January 2023.
  28. Web site: Case Classes . Scala Documentation . 15 May 2023.
  29. Web site: CustomStringConvertible . Apple Developer Documentation . 5 May 2023 . en.
  30. Web site: Documentation . docs.swift.org . 4 May 2023.