Answer the question
In order to leave comments, you need to log in
How to organize access according to DDD canons?
Tell me how to properly organize access to files without violating the principles of DDD?
The requirements are as follows:
Domain Model
Answer the question
In order to leave comments, you need to log in
Greetings!
It seems to me that you are a little bit wrong in following the principles of DDD.
More precisely, you design your code incorrectly.
If you write using DDD, then you don't need to think about the fields in the database or other infrastructure things.
You need to write the domain as it is within your understanding.
Based on your text, I would write this approximately as in the example that I gave. Although it was all written on the knee without analyzing the user's user cases, without analyzing the domain, and so on. So treat my code as an example, or as a draft.
<?php
/**
* "Domain/Directories/File.php"
*/
final class File
{
/**
* Я использую название файла как его уникальный идентификатор.
* Надо учитывать что этот идентификатор уникален в рамках одной директории (папки).
* В случае если это сложно отслеживать можно использовать UUID как ИД и атрибут $name как допольнительнй.
*
* @var string
*/
private $name;
/**
* @var bool
*/
private $hidden = false;
/**
* @param string $name
*/
public function __construct(string $name)
{
$this->name = $name;
}
/**
* Скрываем временно файл.
*/
public function hide() : void
{
$this->hidden = true;
}
/**
* Востанавливаем доступ к срытому файлу.
*/
public function show() : void
{
$this->hidden = false;
}
/**
* @return string
*/
public function getName() : string
{
return $this->name;
}
/**
* @return bool
*/
public function isHidden() : bool
{
return $this->hidden;
}
}
/**
* Aggregate Root.
* По моегму видению файл не может существовать без родительской папке.
* По этому папка и есть наш аггрегат.
*
* "/Domain/Directories/Directory.php"
*/
final class Directory
{
/**
* Название директории (папки) играет роль уникального идентификатора.
* Так-же как и в случае с файлом этот идентифактор может быть уникален только в рамках другой директории (папки).
* В случае если это сложно отслеживать можно использовать UUID как ИД и атрибут $name как допольнительнй.
*
* @var string
*/
private $name;
/**
* @var File[]
*/
private $files = [];
/**
* @var array
*/
private $removedFiles = [];
/**
* @var string
*/
private $ownerId;
/**
* @var bool
*/
private $hidden = false;
/**
* @param string $name
*/
public function __construct(string $name, string $ownerId)
{
$this->name = $name;
$thi->owner = $ownerId;
}
/**
* Этот метод не должен использоватся нигде больше кроме в MySQLStorage или в других сторэджах.
* Это дополнительный метод который нужен потому что другие доменные методы которые описывают бизнес логику могут содержать например события.
* Чтобы не выбрасывать все события при восстановлении используется такого рода хак.
* Вы же можете сипользовать что-то другое если у вас есть более приемлемый вариант.
* Я пока лучше что-то не нашел.
*
* @internal
*/
public function reconstruct(bool $hidden, File ...$files) : void
{
$this->hidden = $hidden;
foreach ($files as $file) {
$this->files[$file->getName()] = $file;
}
}
/**
* @param File $file
*/
public function addFile(File $file) : void
{
$this->files[$file->getName()] = $file;
}
/**
* @param File[] $files
*/
public function addFiles(Files ...$files) : void
{
foreach ($files as $file) {
$this->addFile($file);
}
}
/**
* Это то что вы называете "запретить навсегда".
* Надо трактировать удаление файле как `sof delete`.
* То есть файл не удаляется реально а просто ставится какой небудь флаг типа `deletedAt` которое показывает время удаления.
* Хочу заметить что это один из вариантов реализации, так как их много.
* Может возникнуть вопрос почему мы не делаем это удаление через сам файл `File.php`
* это потому что возможно этот файл удален в одной директиве а в другой нет.
* То есть возможно реальный файл есть на диске а его сслки в БД дублируются или еще что, и каждая директория сама контролирует это для себя.
* Вы можете удалить этот метод, и реальизовать свою логику как вам угодно.
*
* @param string $name
*/
public function removeFile(string $name) : void
{
if (!isset($this->files[$name])) {
throw new OutOfBoundsException('Invalid file ID');
}
$this->removedFiles[] = $name;
}
/**
* Скрываем временно дирекорию.
*/
public function hide() : void
{
$this->hidden = true;
}
/**
* Востанавливаем доступ к срытой директории.
*/
public function show() : void
{
$this->hidden = false;
}
/**
* @return string
*/
public function getName() : string
{
return $this->name;
}
/**
* @return string
*/
public function getOwnerId() : string
{
return $this->ownerId;
}
/**
* @return File[]
*/
public function getFiles() : array
{
return $this->files;
}
/**
* @return array
*/
public function getRemovedFiles() : array
{
return $this->removedFiles;
}
/**
* @return bool
*/
public function isHidden() : bool
{
return $this->hidden;
}
}
/**
* "Domain/Directories/Repository.php"
*/
final class Repository
{
/**
* @var Storage
*/
private $storage;
/**
* @param Storage $storage
*/
public function __construct(Storage $storage)
{
$this->storage = $storage;
}
/**
* @param Directory $directory
*/
public function store(Directory $directory) : void
{
$this->storage->store($directory);
}
/**
* @return array
*/
public function getDirectoriesByOwner(string $ownerId) : array
{
$this->storage->getDirectoriesByOwner($ownerId);
}
}
/**
* "Domain/Directories/Storage.php"
*/
interface Storage
{
/**
* @param Directory $directory
*/
public function store(Directory $directory) : void;
/**
* @return array
*/
public function getDirectoriesByOwner(string $ownerId) : array;
}
/**
* "Infrastructure/Persistence/Directories/MySQLStorage.php"
*/
final class MySQLStorage implments Storage
{
/**
* Название поля в БД.
*/
private CONST TABLE = 'Direcotry';
/**
* @var PDO
*/
private $database;
/**
* @param PDO $database
*/
public function __construct(PDO $database)
{
$this->database = $database;
}
/**
* @param Directory $directory
*/
public function store(Directory $directory) : void
{
// Провекра на сущестование директории в БД.
if (/* Ваша проверка на наличие в БД */) {
// Подгоавливаем директорию для сохранения.
$this->prepareRowData($direcotry);
// TODO: Обновите директорию в БД.
} else {
// Подгоавливаем директорию для сохранения.
$this->prepareRowData($direcotry);
/**
* TODO: Создайте новую директорию в БД.
* Этот процесс вероятнее всего в релационной БД будет содержать несколько шагов.
* Добалвение самой директории (папки), добалвение всех файлов в этой директории возможно в отдельной таблице.
* Ну и все другие операции которые уместны в этом сценарии.
*/
}
}
/**
* @return array
*/
public function getDirectoriesByOwner(string $ownerId) : array
{
/**
* Именно в этом методе и происходит выборка и фильтрация нужных директорий (папок) и файлов.
* Вы легко можете выбрать все файлы кторые не удаленны и не скрытие. Или все, или те которые вы посчитаете нужным.
* `WHERE Directory.isHidden = false AND File.isDeleted = false AND File.isHidden = false`
* То есть хочу заметить что вы свободны использовать любую структуру БД, которая вам подходит.
* Для целесности этой цепочки ниже после выборки данных вызывается метод преобразования из массива в Entity.
*/
// Запрашиваем данные из БД6 после чего преобразовываем все в доменные сущности и возвращаем результат.
$result = [];
foreach ($rows as $row) {
$result = $this->buildEntity($row);
}
return $result;
}
/**
* Этот метод конвертирует Entity в массив для дальнейшего его сохранения.
*
* @return array
*/
private function prepareRowData(Directory $directory) : array
{
$files = [];
foreach ($direcotry->getFiles() as $file) {
$files[] = [
'name' => $file->getName(),
'isHidden' => $file->isHidden(),
'isDeleted' => in_array($file->getName(), $direcotry->getremovedFiles()),
];
}
return [
'name' => $direcotry->getName(),
'ownerId' => $direcotry->getOwnerId(),
'isHidden' => $direcotry->isHidden(),
'files' => $files,
];
}
/**
* @param array $row
*
* @return Directory
*/
private function buildEntity(array $row) : Directory
{
$files = [];
foreach ($row['files'] as $item) {
$files[] = new File($item['name']);
}
$directory = new Directory($row['name']);
$directory->reconstruct($row['isHidden'], ...$files);
return $directory;
}
}
/**
* "Application/Directories/GetDirectoriesByOwner.php"
* Это так называемый `Use Case` или в книжках `Application Service`.
* Все наше приложение работает только через такие юз кэйсы, API, Console, Backend все что у нас есть вызывает такие юз кэйсы.
* Пример:
* $service = new GetDirectoriesByOwner(
* new Repository(
* new MySQLStorage($container[PDO::class])
* )
* );
*/
final class GetDirectoriesByOwner
{
/**
* @var Repository
*/
private $repository;
/**
* @param Repository $repository
*/
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
/**
* @param string $ownerId
*
* @return array
*/
public function handle(string $ownerId) : array
{
return $this->repository->getDirectoriesByOwner($ownerId);
}
}
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question