[DDD] Lazy Load & Value Objects

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Sinds een tijd ben ik bezig met Domain Driven Design. Nu loop ik echter tegen een probleem aan. Beschouw het volgende kleine voorbeeld. Er is een spel met gebruikers en die gebruikers winnen elke keer dat een ronde afgelopen is een prijs; goud, zilver of brons.

In dit voorbeeld is User een entity aangezien hij identiteit heeft. Zo'n Award echter is volgens mij een Value Object omdat ik niet geinteresseerd ben in de identiteit van de Award. Voor zover ik begrepen heb vormen deze twee klassen de Aggregate root, waarbij met behulp van een UserRepository een User opgehaald kan worden. De relatie tussen User en Award is one-to-many, omdat een User meerdere Awards kan hebben.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
class DatabaseUserRepository implements UserRepository
{
    public function getUserById($userId) {
        
        $resultset = $this->database->query("SELECT ...");
        
        if ($resultset->length () > 0) {
            return UserFactory::create($resultset->next());
        } else {
            throw new UserDoesNotExistException();
        }
    }
}


PHP:
1
2
3
4
5
6
class User
{
    public function getAwards() {
        ?
    }
}


Het tonen van de Awards zal weinig voorkomen. Mijn grote vraag is nu; hoe kan je het beste Lazy Load implementeren op Value Objects? Er is namelijk geen DatabaseAwardRepository met een getAwardsByUser(User $user) methode omdat je slechts een Repository hebt voor je Aggregate root. Zo'n getAwardsByUser methode moet je ook niet implementeren op de DatabaseUserRepository. Wat als er een loadAwards(User $user) methode op de DatabaseUserRepository gezet wordt die een opgegeven User object vult met een lijst van Awards.

Kortom: is hier wel sprake van een Value Object? Of zijn het twee aggregates zodat twee Repositories wel noodzakelijk zijn? Kortom: hoe kijken de ervaren DDD-ers hier tegen aan? Hoe zouden jullie dit implementeren?

Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 12-09 23:07
Kan een Award bestaan zonder dat hij aan een User gekoppeld is ?
Indien ja, dan is een Award volgens mij een entiteit , die deel uitmaakt van een aparte Aggregate Root.

Moet je kunnen een lijst van Awards ophalen bv, en zien welke user die bepaalde award gewonnen heeft ?
Indien ja, dan is een Award volgens mij een entiteit die deel uitmaakt van een aparte Aggregate root, en geen value object.

Indien het een value object is, en je aggregate bestaat uit User & Award (waarbij User de Aggregate Root is), dan haal je gewoon de Awards op vanuit je UserRepository.
Een Repository kan nl. over meerdere entities / tabellen gaan.

Ivm het lazy loaden, zou je het jezelf wat makkelijker kunnen maken door een O/R mapper te gebruiken. :)

Je kan er ook voor kiezen om je GetAwards method een UserRepository mee te geven, waarbij de UserRepository een method 'GetAwardsForUser' heeft. (Wel niet zo 'mooi', aangezien dit eerder in een AwardRepository zou moeten komen, aangezien je Awards returned).

Je kan ook een custom collection maken waarin je lazy loading behaviour implementeert ...

Maar, hoe ziet de UserAwards tabel er eigenlijk uit ? Wat zit daarin ?

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Ik hoopte al op een reactie van jou, Alarmnummer of EfbE, maar zo snel had ik niet verwacht :Y)

De database structuur is als volgt (sterk vereenvoudigd):

code:
1
2
3
4
5
6
7
8
9
10
11
12
user
--------
user_id
username
password

award
--------
user_id
name
type
date


Zoals ik in mijn OP al aangaf bestaan er drie soorten Awards. Het is vooralsnog niet noodzakelijk dat we een lijst ophalen van iedereen die een gouden Award heeft. Toegang tot de Awards van een User zal altijd via de User moeten verlopen, en een Award is een weak entity; zonder een bijbehorende User is hij betekenisloos.
Daarom vermoedde ik dat het een Value Object is.

Natuurlijk zou een O/R Mapper erg handig zijn, maar ik zit nu nog met een groot legacy probleem: PHP ;) Omdat ik de beperkingen van PHP al lang inzie ben ik ondertussen bezig met het leren van Spring. Voor nu zit ik echter nog met PHP opgescheept.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
<?php
class User
{
    public function getAwards() {
        if ($this->awards == null) {
            $userRepository = UserRepositoryFactory::getInstance();
            $this->awards = $userRepository->getAwardsByUser($this);
        }
        return $this->awards;
    }
}
?>


Bovenstaande voorbeeld is inderdaad waar ik zelf eerst aan zat te denken. Het probleem hierbij is dat de UserRepository in dit geval een lijst met Award objecten retouneert. Erg lelijk imho. Je zou er ook voor kunnen kiezen om een loadAwardsByTrainer(Trainer $trainer) methode kunnen maken op de UserRepository:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class DatabaseUserRepository implements UserRepository
{
    public function getUserById($userId) {
        ...
    }

    public function loadAwardsByUser(User $user) {
        $sql = '
            SELECT
                *
            FROM
                award
            WHERE
                trainer_id = ' . (int)$user->getUserId() . '
        ';
        
        $resultSet = $this->database->query($sql);
        
        $awards =  new EntityResultset($resultSet, new AwardRowMapper());
        $this->trainer->setAwards($awards);
    }
}

class User
{
    protected $awards;

    public function getAwards() {
        if ($this->awards == null) {
            $userRepository->loadAwardsByUser($this);
        }
        return $this->awards;
    }
}
?>


Voordeel van de tweede methode is dat de UserRepository geen lijst met Awards hoeft te retouneren. Wellicht zou een derde optie nog bruikbaar kunnen zijn door een AwardsProxy te implementeren zodat de Repository voor het User object verborgen blijft. De AwardsProxy is dan een ArrayList die bij de eerste aanroep de UserRepository aanspreekt om zichzelf te laten vullen.