Is dealing with business rules in the model bad practice?

Asked

Viewed 1,899 times

21

This is something that may seem simple, but it is not. After all how to define whether a rule should be in the service layer or in the model itself?

To illustrate, think of the following:

We have an array where you can find a chess set. Every time the user moves a piece (X, Y) it is necessary to validate whether the piece in question is inside the board. Here comes the question: We have the situation of launching an exception if the user is making an invalid move.

The ideal would be to throw this exception by the model and go capturing across the stack to the user? Or to effect this rule on the service layer? The point is that we have all the increment and movement part of the X, Y axes inside the model, so it would be necessary to repeat part of the logic in the service layer.

The question: In this case it is convenient to leave this rule in the model, or replicate part of it for the service would be ideal?

  • 1

    But what is "service layer" here? You’re talking about Controller (the C of MCV)?

  • That’s right, it’s the controller or service layer.

5 answers

24


Forget the patterns (for a moment)

Certain specific problems are best solved if we don’t try to fit everything into some pattern. MVC is not a silver bullet, it is a model, a guide that helps us organize better complex systems. Layers consist of a logical division of responsibilities, but sometimes it is better to consider better the interaction between objects without using fixed roles and stereotypes.

But there are many scenarios, as I believe this to be the case, where the problem is best solved with pure object orientation. Games and other specific cases commonly fall into this category, except, of course, the user interface.

In the end, it almost always ends in Model, but let’s forget it for a moment.

Create your API

One way to think about the problem is by using an abstraction, in this case, an interface that functions as an API and provides the functionality needed for the rest of the system.

Let’s do a modeling exercise!

Well, we’re talking about a game, composed of a chessboard and its pieces, played by two players (even if they are virtual). It seems reasonable to think about the classes:

  • Jogo
  • Tabuleiro
  • Peca
  • Jogador

To start a new game, we could require players' dice or, if it is necessary to continue a saved game, also require a board. For example:

class JogoBuilder {
    Jogo comecar(Jogador j1, Jogador j2);
    Jogo continuar(Jogador j1, Jogador j2, Tabuleiro t);
}

Here comes a matter of taste. I could omit the first method and consider that to start a new game would only need to pass a board in the initial state. For example:

class JogoBuilder {
    Jogo iniciar(Jogador j1, Jogador j2, Tabuleiro t);
}

There could still be a parameter to define whose turn it is:

enum QuemJoga { J1, J2 }

class JogoBuilder {
    Jogo iniciar(Jogador j1, Jogador j2, Tabuleiro t, QuemJoga q);
}

We’d need a way to build the board:

class TabuleiroBuilder {
    Tabuleiro carregar(JogoSalvo j);
    Tabuleiro criarNovo(); //tabuleiro na posição inicial
}

Thinking in terms of API, the class Jogo needs to provide certain services for a client code, which can be a web system, desktop, mobile or even a web service. Something like this:

class Jogo {
    Tabuleiro getTabuleiro();
    Jogador getJogador1();
    Jogador getJogador2();
    QuemJoga getQuemJogaAgora();
}

The interface of Jogo now allows us to know the state of the board, who the players are and who can make the next move. Everything necessary to represent the game. However, something is missing so that you can effectively play.

Well, the person responsible for people’s positions is clearly the Tabuleiro. But to represent the board well, we need to know what’s on each position.

class Posicao {
    byte getLinha();
    byte getColuna();
    Optional<Peca> getPeca(); //pode ou não haver uma peça
}

To represent the board, we could come up with something like this:

class Tabuleiro {
    Posicao[][] getPosicoes(); //permite imprimir o tabuleiro
    Posicao getPosicao(byte linha, byte, coluna); //permite olhar uma posição específica
}

When moving, the player must select the piece and the position to which he will move it. The selection of the part is a UI issue and we should not address this in the API. Let’s imagine that the UI stores the reference for the part at the selected position and then it can inform our API which part will be moved.

However, how can the UI determine where the part can be moved? We should make a new call to the API every time we try and issue an alert in case of an error. A different approach would be to list the possibilities of movement. For example:

class Tabuleiro {
    ...
    Posicao[] getPossibilidadesMovimento(Posicao p);
}

The above method receives the position selected by the user and determines the possible movements. Now, the UI can do something magical like highlight possible destinations as soon as the user selects a piece to move.

If there is a need to test whether a move is valid, the API can provide another method:

class Tabuleiro {
    ...
    boolean ehMovimentoValido(Posicao atual, Posicao desejada);
}

But in general it is not necessary to do this.

Finally, to move the piece:

class Tabuleiro {
   ...
   void movimentarPeca(Posicao atual, Posicao nova) throws MovimentoInvalidoException;
}

Note that in the modeling above, I made two important decisions:

  1. Do not encapsulate the movement. An alternative would be to create a class Movimento to encapsulate the positions. I did not do this because I see no other attribute that can be required, but if in the future the movement could have something more than the two positions it may be an advantage to have a new object.
  2. Treat an invalid move as an error. I did this in this case because my API was designed to give motion options, so in theory an invalid move should not occur and is a mistake. Without, on the other hand, letting the player keep trying to change the position of the piece anywhere on the board, it might be better to trade it for a return of success or failure.

To put the icing on the cake, one last step would be to upgrade the class Jogo with the method that allows the player to effectively play:

class Jogo {
    ....
    void jogar(Jogador atual, Posicao atual, Posicao nova) throws MovimentoInvalidoException, NaoEhAVezDesseJogadorException;
}

The above method must validate if the current player is the owner of the piece. If everything is ok, he moves the piece and switches the turn to the other player.

A possible problem with this implementation is that Tabuleiro is changeable, so someone could do it:

jogo.getTabuleiro().movimentarPeca(...);

This could affect the state of the board without Jogo make the appropriate validations and update the current game status. There are two ways to resolve this:

  1. Do the method Jogo.getTabuleiro return an immutable board. This makes it possible to only affect the board by the Jogo.
  2. Make every change in Tabuleiro create another board. This approach is more "costly" and memory terms, but it would allow us, for example, to have the history of movements.

In the case of the second approach, we could change the method movimentarPeca of Tabuleiro as follows:

class Tabuleiro {
   ...
   Tabuleiro movimentarPeca(Posicao atual, Posicao nova) throws MovimentoInvalidoException;
}

So, with every movement, a new Tabuleiro is returned with the new state and the Game now points to this new object, perhaps storing the previous one in a list, which would allow to do the replay of the game subsequently.

All ready! And at the same time nothing ready! Now it is only implement.

inserir a descrição da imagem aqui

Now go back to the patterns

After the core the game is well modeled we can again think about how to model the system in layers and apply the MVC standard, create the control logic and the look of the application.

  • 1

    +1 for troll face :D

  • 1

    +1 Nice to demonstrate a line of reasoning to develop the core of the game, so to speak (to "forget the word model for a moment" hehehe).

13

At the risk of giving an opinionated response, I would say that the ideal is to put it on the model, because the model is the layer that represents (or should represent) the state of its application, its domain and its business rules, whereas the purpose of the service layer is only to provide a channel of integration between its model/domain and the world outside it.

That is, in my opinion this is not a bad practice. Quite the contrary, not doing this is what is a bad practice.

  • Any and all responses to this topic will be opinionated. Even people who have worked for thousands of years can disagree, there is no exact answer. So until the doubt

  • The point is we have the Part model, for example, this one has its attributes, a series of get and set and so on... and then we start to include business related code in it. There is a concern of mine that sooner or later, with increasing complexity this model becomes too big, with much more code related to the rule than to the object itself. As much as the rule is of the object... rsrs is a controversial subject

  • 1

    @Emirmarques I think you’re starting to get into this: https://en.wikipedia.org/wiki/Anemic_domain_model

  • 3

    +1, and good to see you participating again.

11

I’ll confirm what Victor said. And add that model does not mean a class that encompasses everything it needs in a controller or a view. If you start modeling thinking about how it will be used in controller or view in a 1:1 relationship, you will be doing wrong or hit by coincidence.

The model should facilitate access to the information you need, but not portray everything you need. This is a mistake that has started to be adopted by many because of the rule by the rule. People learn that "in object orientation we must place everything related to the object next to it". This is not true. You may have rules in the same class but this tightens the system. You need to have ways to access the rules, no matter how they have been set up.

In fact almost every OO modeling we see around is wrong. Sometimes it works. Sometimes it seems like it works and the programmer doesn’t even realize it, he goes years without knowing what he’s into. And sometimes it goes very wrong and the programmer, when he has a minimum perception that it is bad, will seek help, where he will receive good and bad guidance. Hopefully he will learn useful design patterns, and has a chance to apply them properly and not in a way formulesca.

What I can guarantee is that if you’re replicating something in the code, you’re doing something very wrong1.

And when it starts to have too much layer, it has another problem too. Often replication is only required by wrong architecture. But without seeing the concrete case, it’s hard to say.

It’s a shame it will throw an exception to an invalid motion case. This is not an exceptional situation. But in Java people think that anything is exceptional, I don’t know if there’s much to change it in people’s minds if language encourages it.

1 Of course, there is a terrible situation where duplication is needed. If you are doing something for the web and need to do a validation, for example, in the client, besides the verification on the server, because it is different languages, you will have to replicate code. The ideal would be to use a tool that automates this.

5

Treating business rules in Model is bad practice?

On the contrary. Model is the right place for business rules.

Being precious

The validation of whether the piece was outside the board is a business rule and needs to be in the Model otherwise the Model will be in an invalid state, and it is the function of the Model itself to ensure its status.

Make an exception? Being even more precious

The Model can have two distinct sets for each user motion attempt.

The first method checks whether the movement is valid.

After consuming this first method to test if the movement is valid, the Controller finally consumes the method that actually executes the movement.

But, strictly speaking, this second method should still launch an exception in the case of invalid movement because, as I said, it is the function of the Model to ensure its status. Unless you opt for a whole design based on error code. Tag Opinion: I don’t see any advantage. Tag Opinion. See this answer: /a/48458/14584.

Why the rule would be in the Controller?

If Controller and Model are both on the server side, there’s no point in writing a business rule in Controller instead of writing to Model.

Controller should contain only application rules.

Replicate rules?

Yes, depending on the experience you want to give the user. But not replicate between Controller and Model! Unless I didn’t understand some particular aspect of its architecture, replicating rules between Controller and Model doesn’t make sense.

Eventually, you may want to replicate Model rules in the higher layer of the system - the browser, for example.

Let’s say you don’t want to spend a request to the server or make the user wait for this request every time it accidentally drops a piece in an unlicensed place - you’ll end up having to do this on the client side as well, although this is a Model rule. That is, it will have code with the same goal in two places. That’s right.

Some frameworks automatically generate client-side validation codes, based on Model-declared validations.

But after all, how to do?

It depends on the purpose of your project.

Is it a school exercise? Then take the opportunity to exercise patterns and good practices and see how painful it is to apply them.

Is it a portal for afionados ordered by a real customer and that needs to deliver early? You can take advantage to ignore patterns and good practices and see how painful it is to continue the project.

Anyway, there are many ways to do it, but you should not regret using the default form: business rules are in the Model, application rules are in the Controller and some validations may be replicated on the customer side to improve the user experience.

  • What is application rules? And what is the difference between application rule and business rule?

  • 1

    @I think I’ve explained this a few times in comments - it seems to be a common question. Does it fit a new question here in the OS?

  • Yeah, that would be a better question, I’ll ask the question.

  • Take a look at question, if you need to edit leave a comment. Then I deleted my comments.

1

It fits in the Model yes, the problem is that some frameworks have equated the Model with the database, or a representation of it (ORM), then automatically it seems that the Controller should implement the business rules.

Once you need to port the app to another platform (e.g. mobile to web) any business rules implemented in the Controller would have to be reimplemented...

When actually below the Model there is still an extra layer that is the database or storage, after all hardly anyone implements a database from scratch. The Controller should only be a varnish that connects Model and View.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.