Joe's
Digital Garden

Model

A model is a particular type in our system representing an Entity in our system. An entity may be serialized to any form of medium (filesystem, database, REST API) however it will be hydrated in memory into an object containing the properties associated with that [[Identity]].

The properties of the model may be simple (integers, floats, booleans, strings) or may be complex (other objects). It is an aggregate of these properties and it's Identity provides the root.

From the storage medium, the model may be hydrated in part or in full. It may be read in immutably or muttably. In general though, I disagree with the "fat" vs "anemic" models concept. The model is best served as a kind of struct as we see in newer languages. It should principly exist to contain and represent it's properties. Methods of mutability or interactions with other Entities are best handled in a [[Application Service]] layer.

The following is a simple example of a Customer model exhibiting both simple and complex properties.

class Customer
{
    private $id;
    private $firstName;
    private $lastName;
    private $addresses;

    public function __construct(
        int $id,
        string $firstName,
        string $lastName,
        array $addresses = []
    } {
        $this->id        = $id;
        $this->name      = $name;
        $this->addresses = $addresses
    }

    public function id(): int
    {
        return $id;
    }

    public function firstName(): string
    {
        return $this->firstName;
    }

    public function lastName(): string
    {
        return $this->lastName;
    }

    public function fullName(): string
    {
        return sprtinf('%s %s', $this->firstName(), $this->lastName());
    }

    public function addresses(): array
    {
        return $this->addresses;
    }
    
    public function addAddress(
        string $street, 
        string $city,
        string $state,
        string $zip
    ): Customer {
        return new self(
            $this->id(),
            $this->firstName(),
            $this->lastName(),
            array_merge($this->addresses(), [
                new CustomerAddress(
                    $street,
                    $city,
                    $state,
                    $zip
                )
            ])
        );
    }
}

All properties of the Customer are immutably set at the time of creation. id, firstName, and lastName are simple properties of primitive types. The addresses property is an immutable array of CustomerAddress types. Adding to the array returns a new instance of Customer, maintaining the immutable state of the original. Methods exist on the model (e.g. fullName) which compute a property at run time which is derrived from either the model's existing properties or a combination of the model properties and method parameters.

It is a good practice to only interact with the model properties, even internally through the getter methods (id(), firstName(), and lastName()) as we should not assume that a property is not a runtime computed value.

The model should not assume the storage medium. This rules out many forms of ORM which attempt to programatically map a database structure to models. This is an anti-pattern as it forces us to structure the design of our application around the limitations of the database. By seperating the model from the persistence layer, we can optimize the design of the model and optimize the persitence layer independently such that the model focuses on it's purpose in the system and the persistence layer focuses on serializing and hydrating from the database.

These rules make models easy to test and cheap to create.

Hydration of a model from the data storage medium (or mediums) is done through the [[Repository]] and Factory patterns.

Linked References