Strongly typed identifier

Last updated
A UML class diagram for a strongly typed identifier. Strongly-typed identifier UML class diagram.svg
A UML class diagram for a strongly typed identifier.

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.

Contents

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).

/// <summary>/// Represents a user identifier./// </summary>/// <param name="Id">The user identifier.</param>publicsealedrecordUserId(GuidId){/// <summary>/// Initializes a new instance of the <see cref="UserId" /> record./// </summary>/// <returns>A new UserId object.</returns>publicstaticUserIdNew()=>new(Guid.NewGuid());publicoverridestringToString()=>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.

structUserId{UserId(conststring_id){id=_id;}stringvalue()const{returnid;}booloperator==(constUserId&rhs)const{returnvalue()==rhs.value();}private:stringid;};ostream&operator<<(ostream&os,constUserId&id){returnos<<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. [4]

require"uuid"# Represents a user identifier.recordUserId,id:Stringdodefinitialize()@id=UUID.v4.to_senddefto_s(io)io<<idenddefself.emptyself.new(UUID.empty.to_s)endend

D

D have immutable structs. [5]

importstd;/** Represents a user identifier. */immutablestructUserId{immutableUUIDid;/** Initializes a new instance of the UserId struct. */this(immutablestringid){this.id=UUID(id);}publicstaticUserIdcreate(){returnUserId(randomUUID.toString());}stringtoString(){returnthis.id.toString();}}

Dart

Dart have classes with operator overloading.

import'package:meta/meta.dart';/// Represents a user identifier.@immutablefinalclassUserId{finalStringid;/// Initializes a new instance of the UserId struct.constUserId(this.id);@overrideoperator==(other)=>otherisUserId&&other.id==id;@overrideintgethashCode=>id.hashCode;@overrideStringtoString()=>id;}

F#

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

openSystem/// <summary>/// Represents a user identifier./// </summary>/// <param name="id">The user identifier.</param>typeUserId(id:Guid)=memberx.id=idstaticmemberNew()=Guid.NewGuid()staticmemberEmpty=Guid.Emptyoverridex.Equals(b)=matchbwith|:?UserIdasp->id=p.id|_->falseoverridex.GetHashCode()=hashidoverridex.ToString()=id.ToString()

Go

Go have structs which provide equality testing. Go however does not provide immutability.

// Represents a user identifier.typeUserIdstruct{idstring}// Creates a new user identifier.funcNewUserId(idstring)UserId{returnUserId{id:id}}func(xUserId)String()string{returnx.id}

Groovy

Groovy have record classes which provide immutability and equality testing. [6]

/** * Represents a user identifier. * * @param id The user identifier. */recordMessage(Stringid){StringtoString(){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.newtypeUserId=UserIdStringderiving(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.

importjava.util.UUID;/** * Represents a user identifier. * @param id The user identifier. */publicfinalrecordUserId(UUIDid){/**     * Initializes a new instance of the UserId record.     * @return A new UserId object.     */publicstaticUserIdnewId(){returnnewUserId(UUID.randomUUID());}publicStringtoString(){returnid.toString();}}

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]

classUserId{#id;constructor(id){if(id==undefined){thrownewTypeError("Argument is null or undefined.");}this.#id=id;Object.freeze(this);}staticempty=newthis.prototype.constructor("00000000-0000-0000-0000-000000000000");staticnew(){returnnewthis.prototype.constructor(crypto.randomUUID());}equals(id){returnidinstanceofthis.constructor&&this.#id===id.valueOf();}toJSON(){returnthis.#id;}toString(){returnthis.#id;}valueOf(){returnthis.#id;}}

Julia

Julia have immutable composite data types. [13]

usingUUIDs"Represents a user identifier."structUserIdid::UUIDendBase.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. */@JvmInlinepublicvalueclassUserId(publicvalid:String){overridefuntoString()=id}

Nim

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

## Represents a user identifier.typeUserId*=distinctstring

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. */finalclassUserIdimplementsJsonSerializable{useStronglyTypedIdentifier;}/** * Provides methods for use with strongly typed identifiers. */traitStronglyTypedIdentifier{/**     * Initializes a new instance of the UserId object.     * @param string $id The user identifier.     */publicfunction__construct(publicreadonlystring$id){}/**     * Creates a new user identifier.     */publicstaticfunctionnew():self{returnnewself(bin2hex(random_bytes(16)));}publicfunctionjsonSerialize():string{return$this->id;}publicfunction__toString():string{return$this->id;}}

Python

Python have 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).

fromdataclassesimportdataclassimportuuid@dataclass(frozen=True)classUserId:"""Represents a user identifier."""id:uuid.UUID@staticmethoddefnew()->Self:"""Create a new user identifier."""return__class__(uuid.uuid4())def__str__(self):returnstr(self.id)

Python also have NewType which can be used to create new data types. [23]

fromtypingimportNewTypeUserId=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'# Represents a user identifier.UserId=Data.define(:id)do# Create a new user identifier.defself.createself.new(SecureRandom.uuid)enddefself.emptyself.new('00000000-0000-0000-0000-000000000000')enddefto_sidendend

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.#[derive(Debug, PartialEq)]pubstructUserId(String);

Scala

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

importjava.util.UUID/** Represents a user identifier.  *  * @constructor  *   Create a new user identifier.  * @param id  *   The user identifier.  */sealedcaseclassUserId(id:UUID)objectUserId:/** Initializes a new instance of the UserId class. */defcreate():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]

importFoundation/// Represents a user identifier.structUserId:CustomStringConvertible,Equatable{privateletid:UUIDinit(_id:UUID){self.id=id}vardescription:String{returnid.uuidString.lowercased}/// Creates a new user identifier.staticfuncnew()->Self{returnSelf(UUID())}}

See also

Related Research Articles

In computing, serialization is the process of translating a data structure or object state into a format that can be stored or transmitted and reconstructed later. When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object. For many complex objects, such as those that make extensive use of references, this process is not straightforward. Serialization of object-oriented objects does not include any of their associated methods with which they were previously linked.

XML-RPC is a remote procedure call (RPC) protocol which uses XML to encode its calls and HTTP as a transport mechanism.

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed. It is a kind of lazy evaluation that refers specifically to the instantiation of objects or other resources.

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other instances of the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. Decorator use can be more efficient than subclassing, because an object's behavior can be augmented without defining an entirely new object.

<span class="mw-page-title-main">Universally unique identifier</span> Label used for information in computer systems

A Universally Unique Identifier (UUID) is a 128-bit label used for information in computer systems. The term Globally Unique Identifier (GUID) is also used, mostly in Microsoft systems.

In object-oriented (OO) and functional programming, an immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created. In some cases, an object is considered immutable even if some internally used attributes change, but the object's state appears unchanging from an external point of view. For example, an object that uses memoization to cache the results of expensive computations could still be considered an immutable object.

In software systems, encapsulation refers to the bundling of data with the mechanisms or methods that operate on the data. It may also refer to the limiting of direct access to some of that data, such as an object's components. Essentially, encapsulation prevents external code from being concerned with the internal workings of an object.

In computer programming, a function object is a construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax. In some languages, particularly C++, function objects are often called functors.

In class-based, object-oriented programming, a constructor is a special type of function called to create an object. It prepares the new object for use, often accepting arguments that the constructor uses to set required member variables.

In computer science, a mutator method is a method used to control changes to a variable. They are also widely known as setter methods. Often a setter is accompanied by a getter, which returns the value of the private member variable. They are also known collectively as accessors.

In some programming languages, const is a type qualifier, which indicates that the data is read-only. While this can be used to declare constants, const in the C family of languages differs from similar constructs in other languages in that it is part of the type, and thus has complicated behavior when combined with pointers, references, composite data types, and type-checking. In other languages, the data is not in a single memory location, but copied at compile time for each use. Languages which use it include C, C++, D, JavaScript, Julia, and Rust.

JSON-RPC is a remote procedure call (RPC) protocol encoded in JSON. It is similar to the XML-RPC protocol, defining only a few data types and commands. JSON-RPC allows for notifications and for multiple calls to be sent to the server which may be answered asynchronously.

In computer science and object-oriented programming, a passive data structure (PDS), also termed a plain old data structure or plain old data (POD), is a record, in contrast with objects. It is a data structure that is represented only as passive collections of field values, without using object-oriented features.

In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object.

In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). The term was coined in 2005 by Eric Evans and Martin Fowler.

In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral (null) behavior. The null object design pattern, which describes the uses of such objects and their behavior, was first published as "Void Value" and later in the Pattern Languages of Program Design book series as "Null Object".

The Web Application Description Language (WADL) is a machine-readable XML description of HTTP-based web services. WADL models the resources provided by a service and the relationships between them. WADL is intended to simplify the reuse of web services that are based on the existing HTTP architecture of the Web. It is platform and language independent and aims to promote reuse of applications beyond the basic use in a web browser. WADL was submitted to the World Wide Web Consortium by Sun Microsystems on 31 August 2009, but the consortium has no current plans to standardize it. WADL is the REST equivalent of SOAP's Web Services Description Language (WSDL), which can also be used to describe REST web services.

This article describes the syntax of the C# programming language. The features described are compatible with .NET Framework and Mono.

Nemerle is a general-purpose, high-level, statically typed programming language designed for platforms using the Common Language Infrastructure (.NET/Mono). It offers functional, object-oriented, aspect-oriented, reflective and imperative features. It has a simple C#-like syntax and a powerful metaprogramming system.

<span class="mw-page-title-main">Nim (programming language)</span> Programming language

Nim is a general-purpose, multi-paradigm, statically typed, compiled high-level systems programming language, designed and developed by a team around Andreas Rumpf. Nim is designed to be "efficient, expressive, and elegant", supporting metaprogramming, functional, message passing, procedural, and object-oriented programming styles by providing several features such as compile time code generation, algebraic data types, a foreign function interface (FFI) with C, C++, Objective-C, and JavaScript, and supporting compiling to those same languages as intermediate representations.

References

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