refactor(repository): make entity deletion idempotent

This commit is contained in:
Jérémy Munsch 2026-08-25 08:26:55 +02:00
parent 3582a3928c
commit 0766e9dc8f
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
10 changed files with 26 additions and 43 deletions

View file

@ -31,8 +31,5 @@ interface ClientRepository extends Repository
public function persist(Client $client): void;
/**
* @throws \Application\Security\Exceptions\ClientNotFoundException
*/
public function delete(Client $client): void;
}

View file

@ -24,8 +24,5 @@ interface BrandRepository extends Repository
public function persist(Brand $entity): void;
/**
* @throws \Domain\Exceptions\BrandNotFoundException
*/
public function delete(Brand $entity): void;
}

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Infra\Adapters\Repositories;
use Application\Security\Exceptions\ClientNotFoundException;
use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Models\ValueObjects\ClientName;
@ -113,10 +112,7 @@ final class ApiClientRepository implements ClientRepository
#[\Override]
public function delete(Client $client): void
{
$deleted = ApiClient::query()->where('id', $client->id->value)->delete();
if ($deleted === 0) {
throw ClientNotFoundException::forClient($client->id);
}
ApiClient::query()->where('id', $client->id->value)->delete();
}
private function toDomain(ApiClient $apiClient): Client

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Infra\Adapters\Repositories;
use Domain\Exceptions\BrandNotFoundException;
use Domain\Models\Entities\Brand;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Domain\Models\ValueObjects\Brand\BrandCode;
@ -124,14 +123,10 @@ final readonly class EloquentBrandRepository implements BrandRepository
#[\Override]
public function delete(Brand $entity): void
{
$deleted = BrandModel::query()
BrandModel::query()
->where('id', $entity->id()->value)
->delete();
if ($deleted === 0) {
throw BrandNotFoundException::forBrand($entity->id());
}
$this->brandCache->invalidate($entity);
}

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Infra\Adapters\Repositories;
use Domain\Exceptions\BrandNotFoundException;
use Domain\Models\Entities\Brand;
use Domain\Models\ValueObjects\Brand\ArcadiaId;
use Domain\Models\ValueObjects\Brand\BrandCode;
@ -75,10 +74,6 @@ final class InMemoryBrandRepository implements BrandRepository
#[\Override]
public function delete(Brand $entity): void
{
if (! isset($this->brands[$entity->id()->value])) {
throw BrandNotFoundException::forBrand($entity->id());
}
unset($this->brands[$entity->id()->value]);
}
}

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Infra\Adapters\Repositories;
use Application\Security\Exceptions\ClientNotFoundException;
use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Models\ValueObjects\ClientName;
@ -48,10 +47,6 @@ final class InMemoryClientRepository implements ClientRepository
#[\Override]
public function delete(Client $client): void
{
if (! isset($this->clients[$client->id->value])) {
throw ClientNotFoundException::forClient($client->id);
}
unset($this->clients[$client->id->value]);
}

View file

@ -183,7 +183,7 @@ describe('ApiClientRepository Integration Tests', function (): void {
]);
});
it('throws ClientNotFoundException when deleting non-existent client', function (): void {
it('is idempotent when deleting non-existent client', function (): void {
// GIVEN
$repository = new ApiClientRepository();
$client = Client::hydrate(
@ -195,9 +195,11 @@ describe('ApiClientRepository Integration Tests', function (): void {
updatedAt: new DateTimeImmutable(),
);
// WHEN & THEN
expect(fn () => $repository->delete($client))
->toThrow(ClientNotFoundException::class);
// WHEN
$repository->delete($client);
// THEN
expect($repository->find($client->id))->toBeNull();
});
it('finds all clients with pagination', function (): void {

View file

@ -246,7 +246,7 @@ describe('EloquentBrandRepository Integration Tests', function (): void {
->and(Cache::has('brand:all'))->toBeFalse();
});
it('throws BrandNotFoundException when deleting non-existent brand', function (): void {
it('is idempotent when deleting non-existent brand', function (): void {
// GIVEN
$brandCache = resolve(BrandCache::class);
$repository = new EloquentBrandRepository($brandCache);
@ -257,8 +257,10 @@ describe('EloquentBrandRepository Integration Tests', function (): void {
arcadiaId: new ArcadiaId('999'),
);
// WHEN & THEN
expect(fn () => $repository->delete($brand))
->toThrow(BrandNotFoundException::class);
// WHEN
$repository->delete($brand);
// THEN
expect($repository->find($brand->id()))->toBeNull();
});
});

View file

@ -63,14 +63,16 @@ describe('InMemoryBrandRepository Unit Tests', function (): void {
expect($repository->find($brand->id()))->toBeNull();
});
it('throws BrandNotFoundException when deleting non-existent brand', function (): void {
it('is idempotent when deleting non-existent brand', function (): void {
// GIVEN
$repository = new InMemoryBrandRepository();
$brand = createBrand('MCQ02', 'Maître CoQ', '2');
// WHEN & THEN
expect(fn () => $repository->delete($brand))
->toThrow(BrandNotFoundException::class);
// WHEN
$repository->delete($brand);
// THEN
expect($repository->find($brand->id()))->toBeNull();
});
it('clears all brands', function (): void {

View file

@ -63,14 +63,16 @@ describe('InMemoryClientRepository Unit Tests', function (): void {
->and($foundAfterDelete)->toBeNull();
});
it('throws ClientNotFoundException when deleting non-existent client', function (): void {
it('is idempotent when deleting non-existent client', function (): void {
// GIVEN
$repository = new InMemoryClientRepository();
$client = createClient('arcadia_id_' . str_repeat('9', 32));
// WHEN & THEN
expect(fn () => $repository->delete($client))
->toThrow(ClientNotFoundException::class);
// WHEN
$repository->delete($client);
// THEN
expect($repository->find($client->id))->toBeNull();
});
it('finds all clients with pagination', function (): void {