2

I am currently thinking about how I would implement a software I did write some years ago with a Clean-/Hexagonal-/Onion-Architecture. This shall not be a "Grand Redesign from Heaven", since I'm not involved in the project anymore, but rather I'd like to learn what I could have done better.

One thing that I have problems realizing for the specific use-cases is the "Tell, don't ask" principle (TDA). Without going in too much details of the problem domain, I'd like to give a short outline of the main use-cases

Basis premises:

  • The data is organized in matches (we have been analyzing a team sport)
  • Each match has a number of players each for the home and the visitor team
  • There is a number of actions for each match (basically players touching the ball)
    • Each action is associated with a player
    • For sake of simplicity I left out sets within the match and player substitutions and the like

Use cases for the match

  • Add home player
  • Add visitor player
  • Add action for home/visitor and roster number

(again, there is much more to it, but I think this will be sufficient to describe my problem)

Analyses of the data within the matches (also over multiple matches, but - simplicity) is central to the application. Within the architecture I'd place the analyses on the second ring, since they are not entities of the application, but still provide central use cases. Analyses rely heavily on the filtering and subsequent counting of actions within the match.

  • Filtering by the player
  • Filtering by other actions (e.g. only take action A if it occurs after action R, but not after action D; only take action A after a good or very good action R; ...)
  • Filtering by quality
  • Calculations based on the qualities of the remaining actions
  • and so forth

Anyway, just asking the match object for its contacts to operate on them would be a violation of TDA. And since the match contains business logic, I feel that I should comply with TDA. On the other hand I don't see a way to add analyses to a match and still keep the analyses definitions on the second ring.

How can I perform analyses of the actions within the match, but still adhere to the TDA? Or shouldn't I operate on a match for the analyses anyway, but directly get the actions from a repository?

var contacts = contactsRepository.GetContactsForMatch(match);

While this would solve the issue for analyses, it would not for saving the contacts for a match - and directly saving from the respective class would certainly violate the SRP and is not in the sense of the architecture paradigms.

  • since the match contains business logic - What business rules do you currently have implemented on your Match object? – Emerson Cardoso Dec 15 '17 at 16:02
  • @EmersonCardoso I have not implemented anything at all, but if I had, I'd - for example - check that no actions that are assigned to a player that is not in the match are added, check that no two player with the same roster number are added, etc. – Paul Kertscher Dec 15 '17 at 16:11
  • @EmersonCardoso Thinking about it this could be counted as a TDA violation on its own right ;) – Paul Kertscher Dec 15 '17 at 16:11
  • Can you give more examples about what analysis you'll implement? Also: just asking the match object for its contacts to operate on them would be a violation of TDA - the analysis will only be a violation of TDA if you retrieve data, and use them in order to change the state of the match object. – Emerson Cardoso Dec 15 '17 at 17:16
  • @EmersonCardoso - this kind of makes sense. This might be the part that I got wrong ;) – Paul Kertscher Dec 18 '17 at 6:11
1

There are several points that come to my mind, though some could be not applicable, since I don't fully realize your specifics.

First of all, to my understanding, Use cases are intrinsically coordinators. They don't do any job except coordinating domain objects' flow:

These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise wide business rules to achieve the goals of the use case.

I realize that this can be interpreted as getting some data with some entity's getter and implementing some logic right in the use case's code, but I think it's not what Robert Martin meant. It just smells with anemic domain model.

So that's where I move to my second point. You notion of TDA I guess is induced by the fact that you don't want your domain model to be anemic. I'm sure you're aware that OOP is about combining data and behavior, so that objects do not expose data, providing their clients with behavior instead.

Third, how would I achieve that. I would use CQS. I would have two separate models: one that's mutating its state and another that contains the calculating logic.

Fourth, how this could be implemented. If you don't have tons of data to operate upon, I incline to using decorators. As a side-effect, they just have to have an access to decorated entities. But all your logic would be contained in your pure domain, not in a use case layer. That's how it could look like:

new Filtered(
    playersFromDBTable(),
    function (Player $player) {
        return come_condition_result($player);
    }
);

And if you do have a lot of data, I'd use repositories for that purpose -- again, read-side repositories.

Fifth, if you have to have this as a business-rule, I'd use the read(calculating)-model that checks that some conditions hold true, and if they are, I would invoke a write model: add a player or take some other action.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.