A basic principle accepted in Object Orientation is the separation of responsibilities.
Behold Principle of Separation of Responsibilities for more details.
Four responsibilities
A widely used design standard identifies at least 4 responsibilities in solving this problem:
1) Entity
Object representing the entity; in this case, a system user. It has the entity’s business attributes and behaviors.
2) Repository
Object that abstracts the persistence and recovery of the entity.
Has CRUD methods, if necessary, and other research methods.
Example:
class UsuarioRepo
Usuario[] usuariosAtivos()
The repository can also be generic, where a single object abstracts the persistence and recovery of all types of system entity:
class Repositorio
T[] obtem<T>(CriteriosPesquisa)
But the Repository does not hold the information of the database structure, nor does it know the specifics of the database being used. In other words, the repository knows nothing about the tables in the database and does not know how to make SQL commands.
To do its work, the repository uses the following objects (and after we talk about them, we return to the repository).
3) Entity to Database Mapping
Based on definitions in XML files or metadata declared in the entity class, this object delivers a mapping between entities and tables and columns of the database.
This object has the knowledge of the structure of the database and its relation to the entities, but it also does not know how to make SQL commands.
4) Interaction with the database
Here we have one or more objects specialized in mounting SQL commands.
There may be an object or a set of objects handling the specifics of each supported database server.
There may also be an abstraction of this interaction with the database, especially in case more than one database is supported.
Back to the Repository
As I said, the repository uses the other objects to do its work. For example:
class UsuarioRepo
Usuario[] usuariosAtivos()
{
var mapeamentoUsuario = mapeamento.get<Usuario>()
var sql = sqlBuilder.select(mapeamentoUsuario).criterio("ATIVO = 'S'")
return conexao.executeSelect<Usuario>(sql)
}
Note that I have abstracted other responsibilities in this response, such as the connection object and the objects that execute commands against the database.
ORM
If you implement this solution, you will have developed a ORM, but you can also use a market.
A ORM provides Entity/Database mapping capabilities and abstracts the connection to the database, the construction of SQL commands and the execution of these commands in the database.
Orms for Java, Ruby (on Rails*) and . Net are very simple to use and although they are complex, they do not add complexity to the project because this complexity is encapsulated within them.
Using an ORM, the code looks something like this:
@Entity("TAB_USUARIO")
class Usuario
@Column("LOGIN")
username
@Column("SENHA")
password
class UsuarioRepo
Usuario[] usuariosAtivos()
{
return orm.query<Usuario>("select u from Usuario u where u.ativo = true").result()
}
Note that this command is not native SQL code of the database and is not about tables, but about entity names according to their class. The ORM, in turn, dealt with the mapping, the specifics of the bank in question, connection pool, etc.
There is still the option to use lambda and other Patterns instead of typing the command as string; and . Net still offers LINQ.
You can still choose not to encapsulate persistence and recovery in repositories but, instead, write the queries every time you need them - as they are queries against entities and not native SQL against tables, you would not be spreading database code through your application. I usually use repositories due to some features of my context.
*Rails uses the model Activerecord, where persistence and recovery behaviors belong to the entity and its class respectively, and not to a separate object.
Completion
Separation of responsibilities can go far beyond writing SQL in a class other than the entity class.
You should make this separation according to the characteristics of your context.
For example, if the software meets a complex need, separating responsibilities well can help prevent the complexity of the domain from contaminating the code. The more complex the domain, the more it compensates for the separation of responsibilities, which helps to keep the code simpler (although this may be a bit counterintuitive).
Finally, you don’t usually need to develop an ORM to help separate responsibilities because there are good frameworks for this available in the market.
This depends a little, some add behaviors in the user class, others separate it into two classes one with only values (a pojo) and the query logic and the like is in a DAO class. You need to see the architecture of your project.
– rray
Just to clarify, since there are votes of closure: I migrated this question from the goal because I do not consider it to be based on opinions. See the answers.
– bfavaretto