RODRIGO BRANAS
10 de Dezembro de 2023

O que é e quando devemos utilizar o padrão Repository? (Repository Pattern)

O propósito do padrão Repository vai além de simplesmente separar as responsabilidades relacionadas com a persistência. Ele é responsável pela mediação e o desacoplamento entre o Domain Model, ou seja, objetos de domínio, e as operações de persistência de cada um desses objetos.

Isso quer dizer que só faz sentido utilizar o padrão Repository em conjunto com o Domain Model? Exatamente, caso contrário o que ele estaria persistindo seria algo diferente de um objeto de domínio e o padrão perderia o sentido e acabariam sendo utilizados outros padrões de persistência como o Table Data gatway ou Data Access Object (também conhecido como DAO), que são orientados a tabelas.

Para entender o Repository é necessário compreender antes que existem dois caminhos em termos de design, um deles é de abstrair as regras de negócio em procedimentos, métodos, funções, transferindo o fluxo de execução entre cada uma delas, apenas passando parâmetros e sem a utilização de abstrações. De acordo com a obra clássica do Martin Fowler, Patterns of Enterprise Application Architecture, essa abordagem se chama Transaction Script e é conhecida por ser anêmica, ou seja, ainda que seja implementada utilizando uma linguagem Orientada a Objetos o design acaba sendo procedural.

Outra abordagem é o Domain Model, que é utilizado no Clean Architecture e no Domain-Driven Design, onde a complexidade é distribuída em objetos de domínio, protegendo a invariância por meio do encapsulamento e a exposição apenas do comportamento necessário para realizar a mutação de estado, ou seja, reduzindo o acoplamento e dessa forma a fragilidade.

Isso cria um problema: como fazer para persistir esses objetos de domínio?

O padrão Repository existe exatamente para isso, realizar a persistência sem que as outras camadas precisem se preocupar com isso.


class User {
	private email Email;
	private password Password;
	private status: string;

	updatePassword (newPassword: string) {
		if (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 é composta por Email e Password, criando um grupo de objetos de domínio. No Domain-Driven Design isso se chama Aggregate e é exatamente esse grupo que é persistido no Repository.

Dessa forma, teremos um UserRepository:


interface UserRepository {
	save (user: User): Promise;
	update (user: User): Promise;
	get (userId: string): Promise;
	list (): Promise;
}

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 invariância. Repare que utilizamos um Value Object para abstrair a senha e para definí-la é necessário causar uma mutação no objeto User que passará pelo objeto Password.


class Password {
	private value: string;

	constructor (password: string) {
		if (password.length < 8) throw new Error("Minimum length is 8");
		if (!password.matches(/\d+/g)) throw new Error("Password must have as least one number");
		if (!password.matches(/[A-Za-z]+/g)) throw new Error("Password must have as least one letter");
		this.value = value;
	}

	getValue () {
		return this.value;
	}
}

Se o método updatePassword existisse ele quebraria a invariância do objeto User, levando-o a um estado inválido, 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 contém uma combinação de números e letras.

O padrão Repository tem em sua interface o objeto User, impedindo que qualquer mutação de estado seja feita sem passar pelas regras de negócio implementadas nos objetos de domínio, preservando a integridade do Domain Model.

Utilizar o padrão Repository não tem nada a ver com certo ou errado, é simplesmente resultado da escolha de uma estratégia de design, que também não está 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 abstração por parte da equipe.


#ddd #domaindrivendesign #repository #designpatterns