Pergunta

Consider the differences in the following two examples of a User Service (here I'm using PHP but the question can apply to any language):

Example 1:

class UserService {

    public function save($id, $data)
    {
        UserRepository::save($id, $data);  // Updates user and user history, etc
    }
}

Example 2:

class UserService {

    public function save($id, $data)
    {
        UserRepository::save($id, $data);        // Updates user
        UserRepository::saveHistory($id, $data);  // Updates user history
        etc...
    }
}

This is a simple example, but it can become difficult in a project to know when we are dealing with "business concerns" as opposed to "data persistence concerns" (I apologize if I'm not using the correct terminology here). In regard to proper SoC with Services and Repositories, are both of these perfectly acceptable ways of using the Repository Pattern? How do we know when to use one over the other?

Foi útil?

Solução 2

It's not about the pattern. It's about the responsibilities. Does a save() always mean save current and save history. Because if does, the first way is better, but the background is important. You should have two model methods for save and savehistory and what logic repository that groups them.

The second variant is wrong not because of pattern way, but because of lack of checks.

I don't know what does save and saveHistory should mean exactly, but let's say you have users table where you store user information including the last login. And user_login_history where you save each login.

In this case save() will try to insert the current timestamp for users lastlogin. But what if it fails? In some circumstances the save() method return false. A momental lack of db connection, timeout, wrong datatypes, etc. But the method returns non-true value. The execution of UserServices::save() will continue after exiting from Repository::save(), and then will go to saveHistory() and will add login_history for this user with the current timestamp, even your user was not able to login. So you will have false data.

If you want to use the second way, do the proper checks. The simpliest way is:

public function save($id, $data)
{
    if(UserRepository::save($id, $data)) {        // Updates user
        UserRepository::saveHistory($id, $data);  // Updates user history
        ... // etc related method based on successful save()
    }
}

Ensure for the right execution of the methods, but stick to the single responsibility.

Outras dicas

The above example is breaking the Single Responsibility Principle. UserRepository should be taking care only of User persistence. History would be usually stored in some "history table" hence the HistoryService. I could imagine it would need some extra logic than just persistence (eg. extracting changed data from entity ...)

Such HistoryService object (we used to call it AuditService) would be injected into the Repository in Dependency Injection Container. The code could look like this:

class UserService {

    private $historyService;

    public function __construct(HistoryService $historyService)
    {
        $this->historyService = $historyService;
    }

    public function save($id, $data)
    {
        UserRepository::save($id, $data);        // Updates user
        $this->historyService->saveHistory('user', $id, $data);  // Updates user history
        etc...
    }
}

To add to the answer of my own question:

I think if the services will NEVER need to work with certain parts of an aggregate, then the Repository can take care of those aggregate parts instead of relying on calls from the Service to do so. The question then becomes: how much is "User History" married to "User". So much that it would never need a Service acting on it independently?

What 'business concerns' are you talking about when your examples shows a lack of any? In both cases it's just n-tier CRUD. In both cases the UserService is pointless class which just redirects atcions to another class.

You're saying "it can become difficult in a project to know when we are dealing with business concerns as opposed to data persistence concerns". It's rather a sign that you don't need DDD. In projects that really needs DDD, the difference is obvious: all create, read, update, delete and synonyms of previous 4 are persistence concerns.

In your domain, those business concerns might be: user signed up for a newsletter, user visited something, buy something. Think about business tasks that are actually performed, not how you would map it to tables.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top