Touraine Tech 2025
Freelancer software craftman
It helped me to ship CRUD applications more quickly
I mainly contributed to developing the resource bundle
Validator
Service
Serializer
Form
Class without any business logic
Akeneo PIM looks like a big CRUD app, but it isn't
Building a business-oriented app with anemic models is tricky
It is more complicated to describe business problems
You can't ensure business invariants
Entities, Value objects and Aggregates
help to design rich domain objects
Do not apply DDD if your application does not involve any business logic
Value object
Entity
Identity-less
Attribute based equality
Immutable
Behavior-rich
Self-validating
Enforce invariant
Focus on behavior (not data)
Has an identity
Identity based equality
An aggregate is a cluster of objects (entities and value objects) that together represent a domain concept
Map
Marker
Media
Name
Location
Path
aggregate root
entity
entity
Value object
Value object
Name
Name
Marker List
Map Gallery
Maker Gallery
Value object
Value object
Value object
Content
Map
Marker
Media
Cartographer
MarkerCategory
ID
ID
ID
Object = behavior + state
shareWithFriend
addAssetsToGallery
Class
addMarker
Constructor
Internal
State
final class Map
{
// Primary constructor
public function __construct(
private readonly string $id,
private readonly Name $name,
private MarkerList $markers,
) {
}
// Secondary constructor
public static function create(string $id, string $name): self
{
return new self(
$id,
new Name($name),
MarkerList::empty()
);
}
// ...
}
final class Map
{
// ...
public function addMarker(
string $markerId, string $name, float $latitude, float $longitude
): void {
$name = new Name($name);
$location = new Location($latitude, $longitude);
$this->markers = $this->markers->add(
new Marker($markerId, $name, $location, $addedAt)
);
$this->viewport = new Viewport($this->markers);
}
// ...
}
Public method
Public method
Class
Public method
Constructor
Internal
State
Getters
findClosestMarker
getMarkers
final class Location
{
public function __construct(
private readonly float $latitude,
private readonly float $longitude,
) {
if (90 < $latitude || -90 > $latitude) {
throw new \OutOfRangeException('The latitude must be between -90 and 90');
}
if (180 < $longitude || -180 > $longitude) {
throw new \OutOfRangeException('The latitude must be between -180 and 180');
}
}
public function equals(Location $location) {
return
$location->latitude === $this->latitude &&
$location->longitude === $this->longitude
;
}
}
final class Map
{
public function addMarker(
string $markerId, string $name, float $latitude, float $longitude
): void {
if ($this->markers->hasMarkerLocatedAt(new Location($latitude, $longitude)) {
throw new \Exception(
'Cannot add the marker, a marker is already located there'
);
}
$name = new Name($name);
$location = new Location($latitude, $longitude);
$this->markers = $this->markers->add(
new Marker($markerId, $name, $location, $addedAt)
);
$this->viewport = new Viewport($this->markers);
}
}
final class MarkerList
{
private array $markers = [];
public function __construct(Marker ...$markers)
{
$this->markers = $markers;
}
public static function empty(): MarkerList
{
return new self();
}
public function add(Marker $marker): MarkerList
{
return new self($marker, ...$this->markers);
}
public function remove(string $markerId): MarkerList { /** ... */ }
public function hasMarkerLocatedAt(Location $location): bool { /** ... */ }
}
You only manipulate data structures
You canot inform the rest of the application about what happens
addMarker
Get and dispatch
Map
records
MarkerAdded
final class MarkerAdded
{
public function __construct(
private readonly string $mapId,
private readonly string $markerId,
private readonly Name $name,
private readonly Location $location,
) {}
}
final class Map
{
public array $events = [];
// ...
public function addMarker(string $markerId, string $name, /** Etc. */): void
{
$name = new Name($name);
$location = new Location($latitude, $longitude);
$this->markers = $this->markers->add(
new Marker($markerId, $name, $location, $addedAt)
);
$this->viewport = new Viewport($this->markers);
$this->events[] = new MarkerAdded($this->mapId, $markerId, $name, $location);
}
}
Feedback is always welcome
On the Basque coast in Bidart
final class Map
{
public static function create(string $mapId, string $name): self {
//...
}
public function addMarker(string $markerId, string $name, /** etc. */): void {
//...
}
public function shareWithFriend(string $cartographerId): void {
//...
}
public function addAssetsToMapGallery(string $assetId, /** etc. */): void {
//...
}
}