Strongly typed identifier
File:Strongly-typed identifier UML class diagram.svg
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
Passing a strongly typed identifier throughout the layers of an example application.
Passing a strongly typed identifier throughout the layers of an example application
= C# =
C# have records which provide immutability and equality testing.{{cite web |title=Records - C# reference |url=https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record |website=learn.microsoft.com |access-date=23 January 2023 |language=en-us}} The record is sealed to prevent inheritance.{{cite web |title=sealed modifier - C# Reference |url=https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/sealed |website=learn.microsoft.com |access-date=23 January 2023 |language=en-us}} It overrides the built-in ToString()
method.{{cite web |title=Object.ToString Method (System) |url=https://learn.microsoft.com/en-us/dotnet/api/system.object.tostring |website=learn.microsoft.com |access-date=14 June 2023 |language=en-us}}
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)
{
///
/// Initializes a new instance of the
///
///
public static UserId New() => new(Guid.NewGuid());
public override string ToString() => Id.ToString();
}
= 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 {
UserId(const string _id)
{
id = _id;
}
string value() const
{
return id;
}
bool operator==(const UserId& rhs) const
{
return value() == rhs.value();
}
private:
string id;
};
ostream& operator << (ostream &os, const UserId &id)
{
return os << id.value() << std::endl;
}
= 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.{{cite web |title=Structs - Crystal |url=https://crystal-lang.org/reference/1.11/syntax_and_semantics/structs.html#records |website=crystal-lang.org |access-date=21 February 2024}}
require "uuid"
- 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)
end
end
= D =
D have immutable structs.{{cite web |title=Structs, Unions - D Programming Language |url=https://dlang.org/spec/struct.html |website=dlang.org |access-date=30 May 2023}}
import std;
/** Represents a user identifier. */
immutable struct UserId
{
immutable UUID id;
/** Initializes a new instance of the UserId struct. */
this(immutable string id)
{
this.id = UUID(id);
}
public static UserId create()
{
return UserId(randomUUID.toString());
}
string toString()
{
return this.id.toString();
}
}
= Dart =
Dart have classes with operator overloading.
import 'package:meta/meta.dart';
/// Represents a user identifier.
@immutable
final class UserId {
final String id;
/// Initializes a new instance of the UserId struct.
const UserId(this.id);
@override
operator ==(other) => other is UserId && other.id == id;
@override
int get hashCode => id.hashCode;
@override
String toString() => id;
}
= 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{ id string }
// Creates a new user identifier.
func NewUserId(id string) UserId { return UserId{id: id} }
func (x UserId) String() string { return x.id }
= Groovy =
Groovy have record classes which provide immutability and equality testing.{{cite web |title=The Apache Groovy programming language - Object orientation |url=https://groovy-lang.org/objectorientation.html#_record_classes_incubating |website=groovy-lang.org |access-date=24 December 2023}}
/**
* Represents a user identifier.
*
* @param id The user identifier.
*/
record UserId(String id) {
String toString() { id }
}
= Haskell =
Haskell can create user-defined custom data types using the newtype
keyword.{{cite web |title=Newtype - HaskellWiki |url=https://wiki.haskell.org/Newtype |website=wiki.haskell.org |access-date=18 June 2023}} 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.{{cite web |title=Record Classes |url=https://docs.oracle.com/en/java/javase/19/language/records.html |website=Oracle Help Center |access-date=24 January 2023}}
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) {
/**
* Initializes a new instance of the UserId record.
* @return A new UserId object.
*/
public static UserId newId() {
return new UserId(UUID.randomUUID());
}
public String toString() {
return id.toString();
}
}
= JavaScript =
This JavaScript example implementation provides the toJSON
method used by the JSON.stringify()
{{cite web |title=JSON.stringify() - JavaScript {{!}} MDN |url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify |website=developer.mozilla.org |access-date=23 January 2023}} function to serialize the class into a simple string instead of a composite data type.
It calls Object.freeze()
to make the instance immutable.{{cite web |title=Object.freeze() - JavaScript {{!}} MDN |url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze |website=developer.mozilla.org |access-date=23 January 2023}}
It overrides the built-in toString()
method{{cite web |title=Object.prototype.toString() - JavaScript {{!}} MDN |url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString |website=developer.mozilla.org |access-date=23 January 2023}} and the valueOf()
method.{{cite web |title=Object.prototype.valueOf() - JavaScript {{!}} MDN |url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf |website=developer.mozilla.org |access-date=23 January 2023}}
class UserId {
#id;
constructor(id) {
if (id == undefined) {
throw new TypeError("Argument is null or undefined.");
}
this.#id = id;
Object.freeze(this);
}
static empty = new this.prototype.constructor("00000000-0000-0000-0000-000000000000");
static new() {
return new this.prototype.constructor(crypto.randomUUID());
}
equals(id) {
return id instanceof this.constructor && this.#id === id.valueOf();
}
toJSON() {
return this.#id;
}
toString() {
return this.#id;
}
valueOf() {
return this.#id;
}
}
= Julia =
Julia have immutable composite data types.{{cite web |title=Types · The Julia Language |url=https://docs.julialang.org/en/v1/manual/types/#Composite-Types |website=docs.julialang.org |access-date=30 May 2023}}
using UUIDs
"Represents a user identifier."
struct UserId
id::UUID
end
Base.string(userId::UserId) = userId.id
= Kotlin =
Kotlin have "inline classes".{{cite web |title=Inline classes {{!}} Kotlin |url=https://kotlinlang.org/docs/inline-classes.html |website=Kotlin Help |access-date=23 January 2023}}
/**
* Represents a user identifier.
*
* @property id The user identifier.
* @constructor Creates a user identifier.
*/
@JvmInline
public value class UserId(public val id: String) {
override fun toString() = id
}
= Nim =
Nim have "distinct types".{{cite web |title=Nim Manual |url=https://nim-lang.org/docs/manual.html#types-distinct-type |website=nim-lang.org |access-date=4 August 2023}}{{cite web |title=Nim by Example - Distinct Types |url=https://nim-by-example.github.io/types/distinct/ |website=nim-by-example.github.io |access-date=4 August 2023}}
- Represents a user identifier.
type UserId* = distinct string
= PHP =
This PHP example implementation implements the __toString()
magic method.{{cite web |title=PHP: Magic Methods - Manual |url=https://www.php.net/manual/en/language.oop5.magic.php#object.tostring |website=www.php.net |access-date=23 January 2023}}
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.{{cite web |title=PHP: JsonSerializable::jsonSerialize - Manual |url=https://www.php.net/manual/en/jsonserializable.jsonserialize.php |website=www.php.net |access-date=23 January 2023}}
The class is declared using the final
modifier keyword to prevent inheritance.{{cite web |title=PHP: Final Keyword - Manual |url=https://www.php.net/manual/en/language.oop5.final.php |website=www.php.net |access-date=23 January 2023}}
PHP has traits as a way to re-use code.{{cite web |title=PHP: Traits - Manual |url=https://www.php.net/manual/en/language.oop5.traits.php |website=www.php.net |access-date=2 May 2023}}
/**
* Represents a user identifier.
*/
final class UserId implements JsonSerializable
{
use StronglyTypedIdentifier;
}
/**
* Provides methods for use with strongly typed identifiers.
*/
trait StronglyTypedIdentifier
{
/**
* Initializes a new instance of the UserId object.
* @param string $id The user identifier.
*/
public function __construct(public readonly string $id) {}
/**
* Creates a new user identifier.
*/
public static function new(): self
{
return new self(bin2hex(random_bytes(16)));
}
public function jsonSerialize(): string
{
return $this->id;
}
public function __toString(): string
{
return $this->id;
}
}
= Python =
Python has data classes which provides equality testing and can be made immutable using the frozen
parameter.{{cite web |title=dataclasses — Data Classes |url=https://docs.python.org/3/library/dataclasses.html |website=Python documentation |publisher=Python Software Foundation |access-date=23 January 2023}} It overrides the __str__
dunder method.{{cite web |title=3. Data model |url=https://docs.python.org/3/reference/datamodel.html#object.__str__ |website=Python documentation |publisher=Python Software Foundation |access-date=12 June 2023}}
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 dataclass
import 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.{{cite web |title=typing — Support for type hints |url=https://docs.python.org/3/library/typing.html#newtype |website=Python documentation |publisher=Python Software Foundation |access-date=17 June 2023}}
from typing import NewType
UserId = NewType('UserId', int)
= Ruby =
Ruby have data classes which provides equality testing and are immutable.{{cite web |title=class Data - Documentation for Ruby 3.3 |url=https://docs.ruby-lang.org/en/master/Data.html |website=docs.ruby-lang.org |access-date=6 February 2023}} 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'
- 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
end
end
= Rust =
In Rust this can be done using a tuple struct containing a single value.{{cite web |title=New Type Idiom - Rust By Example |url=https://doc.rust-lang.org/rust-by-example/generics/new_types.html |website=doc.rust-lang.org |access-date=18 June 2023}} This example implementation implements the Debug
{{cite web |title=Debug in std::fmt - Rust |url=https://doc.rust-lang.org/std/fmt/trait.Debug.html |website=doc.rust-lang.org |access-date=23 January 2023}} and the PartialEq
{{cite web |title=PartialEq in std::cmp - Rust |url=https://doc.rust-lang.org/std/cmp/trait.PartialEq.html |website=doc.rust-lang.org |access-date=23 January 2023}} traits. The PartialEq
trait provides equality testing.
// Represents a user identifier.
- [derive(Debug, PartialEq)]
pub struct UserId(String);
= Scala =
Scala have case classes which provide immutability and equality testing.{{cite web |title=Case Classes |url=https://docs.scala-lang.org/tour/case-classes.html |website=Scala Documentation |access-date=15 May 2023}} 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,{{cite web |title=CustomStringConvertible |url=https://developer.apple.com/documentation/swift/customstringconvertible |website=Apple Developer Documentation |access-date=5 May 2023 |language=en}} and the Equatable
protocol which provides equality testing.{{cite web |title=Documentation |url=https://docs.swift.org/swift-book/documentation/the-swift-programming-language/advancedoperators#Equivalence-Operators |website=docs.swift.org |access-date=4 May 2023}}
import Foundation
/// Represents a user identifier.
struct UserId: CustomStringConvertible, Equatable {
private let id: UUID
init(_ id: UUID) {
self.id = id
}
var description: String {
return id.uuidString.lowercased
}
/// Creates a new user identifier.
static func new() -> Self {
return Self(UUID())
}
}
= Zig =
Zig have structs{{cite web |title=Structs {{!}} zig.guide |url=https://zig.guide/language-basics/structs/ |website=zig.guide |access-date=15 October 2024 |language=en |date=20 April 2024}} with constants but by design does not have operator overloading{{cite web |title=Documentation - The Zig Programming Language |url=https://ziglang.org/documentation/0.13.0/#Operators |access-date=15 October 2024}} and method overriding.
/// Represents a user identifier.
const UserId = struct {
value: i32,
/// Initializes a new instance of the UserId struct.
pub fn init(value: i32) UserId {
return UserId{ .value = value };
}
};
See also
{{Portal|Computer programming}}
References
{{Reflist}}
External links
- https://wiki.c2.com/?PrimitiveObsession
{{Data types}}
{{Design Patterns patterns}}
Category:Articles with example C Sharp code
Category:Articles with example C++ code
Category:Articles with example D code
Category:Articles with example Haskell code
Category:Articles with example Java code
Category:Articles with example JavaScript code
Category:Articles with example Julia code
Category:Articles with example PHP code
Category:Articles with example Python (programming language) code
Category:Articles with example Ruby code
Category:Articles with example Rust code
Category:Articles with example Scala code