Tem uns testes aqui que acredito que a legibilidade seria muito melhor se tivéssemos uma comparação de strings entre a resposta esperada e a efetiva:
// [..] setup dos dados
$firstPage = actionToFetchFirstPage();
$expectedFirstPage = <<<EOL
{
"data":[
{
"name":"xthiago",
"files":[
{
"id":"3207c5d2-a9a7-4c56-bc57-2a4249e11357",
"name": "File 1",
"uri":"https://google.com/file1"
},
{
"id":"3207c5d2-a9a7-4c56-bc57-2a4249e11357",
"name": "File 2",
"uri":"https://google.com/file1"
}
]
}
]
}
EOL;
$this->assertEquals($expectedFirstPage, $firstPage);
O problema é com a forma como o arquivo é adicionado:
$user = new User();
$user->addFile('File1', 'https://google.com/file1');
$user->addFile('File2', 'https://google.com/file2');
A implementação de User::addFile
é algo como:
class User
{
public function addFile(
string $fileName,
string $fileUri
): void {
$file = File::create(
Id::generate(), // <-- ID generado
$fileName,
$fileUri,
$this->id(), // <-- File precisa do Id ser User.
);
$this->files->add($file);
}
}
Com esse approach a construção do Value Object File
é responsabilidade do método user::addFile
. O contrato fica simples e encapsulado nele.
O grande problema reside no gerador de ID (Id::generate()
) que trará UUIDs aleatórios.
Possibilidades para resolver isso:
Inverter a dependência e fazer com que o ID seja passado para File::addFile
.
class User
{
public function addFile(
Id $fileId,
string $fileName,
string $fileUri
): void { /* .. */ }
}
// código cliente
$user->addFile($aGivenId ?? Id::generate(), $fileId, $fileUri);
Fazer com que File::addFile
receba uma instância de File
pronta.
class User
{
public function addFile(
File $newFile
): void
{
if (! $newFile->belongsTo($this->id)) {
throw new InvalidArgumentException();
}
$this->files[] = $newFile;
}
}
// código cliente
$aFile = new File(
$aGivenId ?? Id::generate(), $fileId, $fileUri,
$fileId,
$fileUri,
$user->id()
);
$user->addFile($aFile);
Alterar a implementação de Id::generate
para considerar diferentes estratégias de geração de ID. Haveria um método para definir a estratégia a ser usada:
// Define uma estratégia de geração de ID. Se o método não for acionado, a estratégia padrão será usada (uuid).
Id::setGenerationStrategy(GenerationStrategy $strategy);
// Estratégias possíveis:
- `UUIDGenerator` - usa um gerador de UUID - padrão.
- `InMemorySequenceGenerator` - gera IDs inteiros positivos de forma sequencial
- `CallableGenerator`- recebe uma função responsável por gerar segundo seus próprios critérios
As 2 últimas estratégias são interessantes para testes. O teste original poderia ser refatorado para algo como:
Id::setGenerationStrategy(new InMemorySequenceGenerator());
// [..] setup dos dados
$firstPage = actionToFetchFirstPage();
$expectedFirstPage = <<<EOL
{
"data":[
{
"name":"xthiago",
"files":[
{
"id":"1",
"name": "File 1",
"uri":"https://google.com/file1"
},
{
"id":"2",
"name": "File 2",
"uri":"https://google.com/file1"
}
]
}
]
}
EOL;
$this->assertEquals($expectedFirstPage, $firstPage);
Lembrando que o
Id
do agregadoUser
já é recebido pelo construtor e totalmente controlado nos testes unitários. O cerne da questão aqui é sobre um value object do agregado (File
).Todas abordagens tem prós e contras. Qual seu feeling?