O proposito do padrao Repository vai além de simplesmente separar as responsabilidades relacionadas com a persistencia. Ele e responsavel pela mediacao e o desacoplamento entre o Domain Model, ou seja, objetos de domínio, e as operacoes de persistencia de cada um desses objetos.
Isso quer dizer que so faz sentido utilizar o padrao Repository em conjunto com o Domain Model? Exatamente, caso contrario o que ele estaria persistindo seria algo diferente de um objeto de domínio e o padrao perderia o sentido e acabariam sendo utilizados outros padrões de persistencia como o Table Data Gateway ou Data Access Object (também conhecido como DAO), que são orientados a tabelas.
Design Patterns: Transaction Script vs Domain Model
Para entender o Repository e necessario compreender antes que existem dois caminhos em termos de design. Um deles abstrai as regras de negocio em procedimentos, metodos e funcoes, transferindo o fluxo de execução entre cada uma delas, apenas passando parametros e sem a utilização de abstracoes. De acordo com a obra classica do Martin Fowler, Patterns of Enterprise Application Architecture, essa abordagem se chama Transaction Script e e conhecida por ser anemica, ou seja, ainda que seja implementada utilizando uma linguagem Orientada a Objetos o design acaba sendo procedural.
Outra abordagem e o Domain Model, que e utilizado no Clean Architecture e no Domain-Driven Design, onde a complexidade e distribuida em objetos de domínio, protegendo a invariancia por meio do encapsulamento e a exposicao apenas do comportamento necessario para realizar a mutacao de estado, reduzindo o acoplamento e dessa forma a fragilidade.
O Problema: Como Persistir Objetos de Domínio?
Isso cria um problema: como fazer para persistir esses objetos de domínio?
O padrao Repository existe exatamente para isso, realizar a persistencia sem que as outras camadas precisem se preocupar com isso.
Exemplo Prático: Classe User
class User {
private email: Email;
private password: Password;
private status: string;
updatePassword(newPassword: string) {
if (this.status === "blocked") throw new Error("Cannot change password of a blocked user");
this.password = new Password(newPassword);
}
getPassword() {
return this.password.getValue();
}
}
Repare que a classe User e composta por Email e Password, criando um grupo de objetos de domínio. No Domain-Driven Design isso se chama Aggregate e e exatamente esse grupo que e persistido no Repository.
Dessa forma, teremos um UserRepository:
interface UserRepository {
save(user: User): Promise<void>;
update(user: User): Promise<void>;
get(userId: string): Promise<User>;
list(): Promise<User[]>;
}
Pergunta Frequente: updatePassword no Repository?
Ah, mas eu queria simplesmente mudar a senha, não posso ter um updatePassword em UserRepository?
Aqui entra o conceito de Domain Model e da proteção da invariancia. Repare que utilizamos um Value Object para abstrair a senha e para defini-la e necessario causar uma mutacao no objeto User que passara pelo objeto Password.
class Password {
private value: string;
constructor(password: string) {
if (password.length < 8) throw new Error("Minimum length is 8");
if (!password.match(/\d+/g)) throw new Error("Password must have at least one number");
if (!password.match(/[A-Za-z]+/g)) throw new Error("Password must have at least one letter");
this.value = password;
}
getValue() {
return this.value;
}
}
Se o metodo updatePassword existisse ele quebraria a invariancia do objeto User, levando-o a um estado invalido, ou seja, permitindo que alguém modifique a senha de um usuário bloqueado ou utilize uma senha com menos de 8 caracteres e que não contem uma combinação de números e letras.
Preservando a Integridade
O padrao Repository tem em sua interface o objeto User, impedindo que qualquer mutacao de estado seja feita sem passar pelas regras de negocio implementadas nos objetos de domínio, preservando a integridade do Domain Model.
Conclusão: Não é Certo ou Errado
Utilizar o padrao Repository não tem nada a ver com certo ou errado, e simplesmente resultado da escolha de uma estratégia de design, que também não esta certa ou errada, simplesmente vai apresentar vantagens e desvantagens dependendo da situação. Em um domínio muito complexo, utilizar Transaction Script faz com que a complexidade se acumule e com que seja mais difícil manter e evoluir o código-fonte com o passar do tempo enquanto o Domain Model proporciona uma melhor distribuição da complexidade, facilidade em testar no nível de unidade mas ao mesmo tempo requer uma capacidade maior de abstracao por parte da equipe.