ipalaus's avatar

Combining CommandBus with a Repository Pattern & Relations

Hi,

I've been using for quite a while the CommandBus in a project, the fact is that make's my life easier but I'm not sure who to handle more complex scenarios while maintaining the idea of CommandBus and a Repository Pattern. To put you inline, I'll show you a very basic CRUD operation within my app.

Actual working example

<?php

class SeriesController extends BaseController {

    public function store()
    {
        extract(Input::only('name', 'description', 'category_id'));

        $series = $this->execute(new RegisterSeriesCommand($name, $description, $category_id));

        return Redirect::route('backoffice.series.show', $series->id);
    }
}

class RegisterSeriesCommand {

    public $name;
    public $slug;
    public $description;
    public $category_id;

    public function __construct($name, $description, $category_id)
    {
        $this->name = $name;
        $this->slug = Str::slug($name);
        $this->description = $description;
        $this->category_id = $category_id;
    }

}

class RegisterSeriesCommandHandler implements CommandHandler {

    public function __construct(SeriesRepositoryInterface $repository)
    {
        $this->repository = $repository;
    }

    public function handle($command)
    {
        $serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
        return $this->repository->save($serie);
    }

}

class EloquentSeriesRepository extends EloquentRepository implements SeriesRepositoryInterface {

    public function save(SeriesEntity $series)
    {
        $model = new SeriesEloquent($series);

        if ($model->save()) return $series->setRawAttributes($model);

        return false;
    }

}

This is an example of how a category could be created, very convenient.

Adding tags (list of ids) to a category

My problem is, I would like to introduce a tags related to the series I just created. This tags, in my database, are created in a table tags, they're unique and related to a series with a table series_tags. The input form would send a group of id's that belongs to the tags table.

Where do you usually perform the action to attach tags to a series? To be clear I'm using Eloquent but I never return an instance of Eloquent and my Repositories don't accept an instance of a Eloquent model. You can't do a $series->attach($tags);.

One scenario I could see is the next:

<?php

class RegisterSeriesCommandHandler implements CommandHandler {

    public function handle($command)
    {
        $serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
        $serie->tags = $tags; // a collection of TagEntity
        return $this->repository->save($serie);
    }

}

class EloquentSeriesRepository extends EloquentRepository implements SeriesRepositoryInterface {

    public function save(SeriesEntity $series)
    {
        $model = new SeriesEloquent($series);

        if ($model->save()) {
            // yeah, this would be bettern attaching an array of ids
            foreach ($series->tags as $tag) $model->tags()->attach($tag->id);

            return $this->fill($model);
        }

        return false;
    }

    protected function fill(SeriesEloquent $model)
    {
        $entity = new SeriesEntity;
        $entity->setRawAttributes($model);
        $entity->tags = $model->tags;
        return $entity;
    }

}

Each implementation of SeriesRepositoryInterface should know how to handle the tags associated in a entity. In this scenario, Eloquent, knows how to attach it once the model is created, so I think it's fine implementing it like this. Thoughts?

Adding tags (a list of strings) to a category

Cool, what about if I told you that we are not providing a list of IDs but the name of the tag? Some of them may exist, some other could be new. BOOM.

What first came to my mind was doing the next. I'm not sure how does it fit, but it works. I really need feedback on approaches or suggestions:

<?php

class RegisterSeriesCommandHandler implements CommandHandler {

    public function __construct(SeriesRepositoryInterface $seriesRepository, TagRepositoryInterface $tagRepository)
    {
        $this->seriesRepository = $seriesRepository;
        $this->tagRepository = $tagRepository;
    }

    public function handle($command)
    {
        $serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
        $serie->tags = $this->tags($command->tags);

        return $this->repository->save($serie);
    }

    protected function tags(array $tags = [])
    {
        $entities = new Collection;

        foreach ($tags as $tag) {
            $collection->push($this->tagRepository->findOrCreate($tag));
        }

        return $entities;
    }

}

My eloquent implementation of SeriesRepositoryInterface would be the same as the second example, but we're creating/validating the list of tags provided by name.

How this sounds to you? Do you think this is a proper way of doing it respecting the repository pattern and the command bus?

What about file uploads?

If both of the previous examples are super straight what I don't know where to start is with files attached to a category. Let's say that a category can have multiple files. Files are stored in a files table and related to a category with category_files table. Everything is sent within the same request, the user just see a form. I guess my command should have a new attribute for an array of files and then, as we handle our command save it to the storage and then attach the files.

<?php

class RegisterSeriesCommand {

    public $name;
    public $slug;
    public $description;
    public $category_id;
    public $files;

    public function __construct($name, $description, $category_id, $files)
    {
        $this->name = $name;
        $this->slug = Str::slug($name);
        $this->description = $description;
        $this->category_id = $category_id;
        $this->files = $files;
    }

}

class RegisterSeriesCommandHandler implements CommandHandler {

    public function __construct(SeriesRepositoryInterface $seriesRepository, FileRepositoryInterface $fileRepository)
    {
        $this->seriesRepository = $seriesRepository;
        $this->fileRepository = $fileRepository;
    }

    public function handle($command)
    {
        $serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
        $serie->files = $this->files($command->files);

        return $this->repository->save($serie);
    }

    protected function files(array $files = [])
    {
        $entities = new Collection;

        foreach ($files as $file) {
            // here we will save file to the desired storage, if it's OK
            // create a new entity that we can later save in our collection
            $entity = $this->fileRepository->create($file['name'], $file['whatever']);

            $collection->push($entity);
        }

        return $entities;
    }

}

class EloquentSeriesRepository extends EloquentRepository implements SeriesRepositoryInterface {

    public function save(SeriesEntity $series)
    {
        $model = new SeriesEloquent($series);

        if ($model->save()) {
            foreach ($series->tags as $tag) {
                $model->tags()->attach($tag->id);
            }

            foreach ($series->files as $file) {
                $model->files()->attach($file->id);
            }

            return $this->fill($model);
        }

        return false;
    }

    protected function fill(SeriesEloquent $model)
    {
        $entity = new SeriesEntity;
        $entity->setRawAttributes($model);
        $entity->tags = $model->tags;
        $entity->files = $model->files;
        return $entity;
    }

}

What do you think of this way of processing a form that apart of basic text inputs will send a comma delimited list of tags that could be or not in the database, plus some <input type="file"> that would be saved to the storage and later associated to our entity and persisted to the repository using Eloquent?

Thanks in advance Isern

0 likes
3 replies
patrickheeney's avatar

I am going through the same thing (we talked about this on IRC a bit). For completeness I will respond here as well.

I was originally thinking a service layer could handle building up the objects and dispatching them to the correct repository. However, that requires the Service layer to know too much about how the data is stored which violates some principles.

My thought process is now that the Command Handler or Controller, wherever your logic lives, should build up a DTO Entity. This Entity would have all the relations, attributes, etc of the data that needs stored. You then pass this DTO Entity to the Repository to be saved. If you had a MongoRepository it would take that DTO Entity and convert it to the right format and store it all as a single collection (since it doesn't have relations). If you have a SqlRepository then it would take that DTO and save the Model and the related Models based on the Entity values.

So in your example above, your Command Handler knows too much about how the data is stored. It knows that you need a FileRepository. You could not then switch to a different backend where the files are stored as part of the document (nosql, mongo, etc) without rewriting your Command Handler because those data stores do not need a FileRepository. I am now thinking, in your repository you should interact with the File Model directly as it should be a HasMany relation to your Series Repository. Your EloquentRepository should know how to store that Entity for Eloquent and interact with the models.

If you have a Command Handler that updates two unrelated entities (that have no relation), then I think you would inject both repositories and update them accordingly. Lets say you have a membership plan and you upgraded from the free to premium tier. You might have a UpgradeToPremiumHandler that creates an order with the OrderRepository, updates your account status with AccountRepository to be stored for later. These repositories are unrelated to each other so you would need to interact with each one in your Command Handler. This might not be the best example, but that is what I am thinking currently.

I am going through the process of figuring out what to put where, and what responsibility each has. I am documenting my findings here: https://gist.github.com/patrickheeney/c4a192cff86194c859f5

Feel free to comment over there if you have any critiques or improvements.

1 like
faisal.arbain@gmail.com's avatar

@ipalaus do you have your working code in github? how your SeriesEloquent looks like. I saw you accept entity in your constructor.

 public function save(SeriesEntity $series)
    {
        $model = new SeriesEloquent($series);
...
ipunkt's avatar

Jeffrey does the Repository stuff in the controller action in one of the videos. He said, that the repository is controller-dependent thing. So he creates the eloquent entity within the command handler and fetches it as execute() result. This model he stores with the repository->save() method.

But in my opinion the repository thing belongs to the command handler, so i can execute the command either from console and does not have to handle the repository twice (controller and console command).

So I prefer the repository within the command handler.

Please or to participate in this conversation.