Subscribe to PHP Freaks RSS

When and where to determine the ID of an entity

syndicated from planet-php.net on May 29, 2018

This is a question that always pops up during my workshops: when and where to determine the ID of an entity? There are different answers, no best answer. Well, there are two best answers, but they apply to two different situations.



Auto-incrementing IDs, by the database



Traditionally, all you need for an entity to have an ID is to designate one integer column in the database as the primary key, and mark it as "auto-incrementing". So, once a new entity gets persisted as a record in the database (using your favorite ORM), it will get an ID. That is, the entity has no identity until it has been persisted. Even though this happens everywhere, and almost always; it's a bit weird, because:



  • The application logic now relies on some external system to determine the identity of an entity we create.
  • The entity will have no identity at first, meaning it can be created with an invalid (incomplete) state. This is not a desirable quality for an entity (for almost no object I'd say).

It's pretty annoying to have an entity without an identity, because you can't use its identity yet; you first have to wait. When I'm working with entities, I always like to generate domain events (plain objects), which contain relevant values or value objects. For example, when I create a Meetup entity (in fact, when I "schedule it"), I want to record an event about that. But at that point, I have no ID yet, so I actually can't even record that event.



namespace Domain;

final class Meetup { public static function schedule(Name $name, /* ... */): Meetup { $meetup = new self();

$meetup->recordThat( new MeetupScheduled( // we don't know the ID yet! ) );

// ...

return $meetup; } }


The only thing I can do is record the event later, outside the Meetup entity, when we finally have the ID. But that would be a bit sad, since we'd have to move the construction of the event object out of the entity, breaking up the entity's originally excellent encapsulation.



Determining uniqueness



The only way to solve this problem is to determine a new identity upfront. That way, the entity can be complete from the start, and it can record any event it likes, which likely includes the entity's ID itself. One way to do this is to use a UUID generator to generate a universally unique identifier. A first step would be to generate the ID inside the entity's constructor:



namespace Domain;

use Infrastructure\Uuid;

final class Meetup { private $meetupId;

// ...

public static function schedule(Name $name, /* ... */): Meetup { $meetup = new self();

$meetup->meetupId = MeetupId::fromString(Uuid::uuid4()->toString());

// ...

return $meetup; } }


However, generating a UUID is a process that relies on the current date/time and some freshly generated random data. This process is something that - according to the rules explained in "Layers, ports & adapters - Part 2: Layers" - doesn't belong inside the domain layer. It's infrastructure. So the ID generation process itself should happen outside of the entity.



Besides, even though it's technically possible to generate a UUID inside an entity, it's something that conceptually isn't right. The idea behind an ID is that it's unique for the kind of thing it identifies. The entity is only aware of itself, and can never reach across its own object boundaries to find out if an ID it has generated is actually unique. That's why, at least conceptually, generating an identity should not happen inside the entity, only outside of it.



So a better approach would be to generate the ID before creating the new entity, and to pass it in as a constructor argument:



namespace Domain;

final class Meetup { private $meetupId;

// ...

public static function schedule(MeetupId $meetupId, Name $name, /* ... */): Meetup { $meetup = new self();

$meetup->meetupId = $meetupId;

// ...

return $meetup; } }

$meetupId = MeetupId::fromString(Uuid::uuid4()->toString()); $meetup = Meetup::schedule($meetupId, ...);


Generate the ID in the application service



When you have a separate application layer (with application services, e.g. command handlers), you can generate the ID there. For example:



namespace Application;

use Domain\MeetupRepository; use Domain\MeetupId;

final class ScheduleMeetupHandler

Truncated by Planet PHP, read more at the original (another 6377 bytes)