Domain objects should use strongly typed identifier as ids of Domain objects. It will make the code more readable, APIs self documenting and less error prone.

How many times have we seen below pattern. We pass an userId that we are sure exists only to not find the User.

Order orderFactory.createOrder(Long userId, Long productId);

Now here both the arguments are of type Long and it is interchangeable with any other Long - productId and userId could get mixed up or entirely with another id invoiceId.

Contrast the above with below

Order orderFactrory.createOrder(UserId userId, ProductId productId);

No room for error here. Type safety is ensured by the compiler.

A downside of having typed identifiers is that we end up with too many boiler plate Id classes. With Java generics we could avoid that.

import java.util.Objects;

public final class Id<T> implements Comparable<Id<T>> {
    private final Long id;

    public Id(Long id) {
        this.id = id;
    }

    public Long getId() {
        return this.id;
    }
    
    @Override
    public int compareTo(Id<T> other) {
    	return id.compareTo(other.id);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Id<?> id1 = (Id<?>) o;
        return id.equals(id1.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return id.toString();
    }

    public static <T> Id<T> of(Long id) {
        return new Id<>(id);
    }

The above class can be used as below in a domain object

public class Product {
    private Id<Product> productId;
}

And the factory method example will be

Order orderFactrory.createOrder(Id<User> userId, Id<Product> productId);

The factory method of on the Id class can be used to create and instance in a type safe manner. Just make sure that a productId is passed to the of function.  

Id<Product> productId = Id.of(longProductId);

Strongly typed identifiers make the code more expressive and less error prone. The generified Id class here can reduce the boiler plate code required for each domain objects's identifier.