Esta é uma transcrição mais bem-formada do meu comentário inicial na sua pergunta. As respostas às perguntas abordadas pelo OP podem ser encontradas na parte inferior desta resposta. Além disso, verifique a nota importante localizada no mesmo local.
O que você está descrevendo atualmente, Sipo, é um padrão de design chamado Registro ativo . Como em tudo, mesmo este encontrou seu lugar entre os programadores, mas foi descartado em favor dos padrões do repositório e do mapeador de dados por uma simples razão, escalabilidade.
Em resumo, um registro ativo é um objeto que:
- representa um objeto em seu domínio (inclui regras de negócios, sabe como lidar com determinadas operações no objeto, como se você pode ou não alterar um nome de usuário e assim por diante),
- sabe como recuperar, atualizar, salvar e excluir a entidade.
Você aborda vários problemas com o seu design atual e o principal problema do seu design é abordado no último, sexto, ponto (por último, mas não menos importante, eu acho). Quando você tem uma classe para a qual está projetando um construtor e nem sabe o que o construtor deve fazer, a classe provavelmente está fazendo algo errado. Isso aconteceu no seu caso.
Mas fixar o design é realmente bastante simples, dividindo a representação da entidade e a lógica CRUD em duas (ou mais) classes.
É assim que seu design se parece agora:
Employee
- contém informações sobre a estrutura do funcionário (seus atributos) e métodos sobre como modificar a entidade (se você decidir seguir o caminho mutável), contém lógica CRUD para a Employee
entidade, pode retornar uma lista de Employee
objetos, aceita um Employee
objeto quando deseja atualizar um funcionário, pode retornar um único Employee
através de um método comogetSingleById(id : string) : Employee
Uau, a classe parece enorme.
Esta será a solução proposta:
Employee
- contém informações sobre a estrutura do funcionário (seus atributos) e métodos para modificar a entidade (se você decidir seguir o caminho mutável)
EmployeeRepository
- contém lógica CRUD para a Employee
entidade, pode retornar uma lista de Employee
objetos, aceita um Employee
objeto quando você deseja atualizar um funcionário, pode retornar um único Employee
por meio de um método comogetSingleById(id : string) : Employee
Você já ouviu falar de separação de preocupações ? Não, você vai agora. É a versão menos rigorosa do Princípio da Responsabilidade Única, que diz que uma classe deve realmente ter apenas uma responsabilidade, ou como o tio Bob diz:
Um módulo deve ter um e apenas um motivo para mudar.
É bastante claro que, se eu era capaz de dividir claramente sua classe inicial em duas que ainda possuem uma interface bem arredondada, a classe inicial provavelmente estava fazendo muito, e foi.
O que é ótimo no padrão de repositório, ele não apenas atua como uma abstração para fornecer uma camada intermediária entre o banco de dados (que pode ser qualquer coisa, arquivo, noSQL, SQL, orientado a objetos), mas nem precisa ser concreto. classe. Em muitas linguagens OO, você pode definir a interface como real interface
(ou uma classe com um método virtual puro, se você estiver em C ++) e, em seguida, terá várias implementações.
Isso elimina completamente a decisão de se um repositório é uma implementação real. Você simplesmente depende da interface, dependendo de uma estrutura com a interface
palavra - chave. E repositório é exatamente isso, é um termo sofisticado para abstração da camada de dados, mapeando dados para seu domínio e vice-versa.
Outra grande coisa sobre separá-lo em (pelo menos) duas classes é que agora a Employee
classe pode gerenciar claramente seus próprios dados e fazê-lo muito bem, porque não precisa cuidar de outras coisas difíceis.
Pergunta 6: Então, o que o construtor deve fazer na Employee
classe recém-criada ? É simples Ele deve aceitar os argumentos, verificar se eles são válidos (como uma idade provavelmente não deve ser negativa ou o nome não deve estar vazio), gerar um erro quando os dados forem inválidos e se a validação aprovada atribuir os argumentos a variáveis privadas da entidade. Agora ele não pode se comunicar com o banco de dados, porque simplesmente não tem idéia de como fazê-lo.
Pergunta 4: Não é possível responder a todos, de modo geral, porque a resposta depende muito do que exatamente você precisa.
Pergunta 5: Agora que você separou a classe inchado em dois, você pode ter vários métodos de atualização diretamente na Employee
classe, como changeUsername
, markAsDeceased
, que irá manipular os dados da Employee
classe apenas na RAM e, em seguida, você poderia introduzir um método como registerDirty
do Padrão de Unidade de Trabalho para a classe do repositório, através da qual você deixaria o repositório saber que este objeto alterou propriedades e precisará ser atualizado depois que você chamar o commit
método.
Obviamente, para uma atualização, um objeto precisa ter um ID e, portanto, já deve ser salvo, e é responsabilidade do repositório detectar isso e gerar um erro quando os critérios não forem atendidos.
Pergunta 3: Se você optar por seguir o padrão da Unidade de Trabalho, o create
método será agora registerNew
. Se não, provavelmente eu chamaria isso save
. O objetivo de um repositório é fornecer uma abstração entre o domínio e a camada de dados; por isso, recomendo que este método (seja registerNew
ou save
) aceite o Employee
objeto e que cabe às classes que implementam a interface do repositório, que atribui eles decidem se retirar da entidade. Passar um objeto inteiro é melhor, assim você não precisa ter muitos parâmetros opcionais.
Pergunta 2: Agora os dois métodos farão parte da interface do repositório e não violam o princípio da responsabilidade única. A responsabilidade do repositório é fornecer operações CRUD para os Employee
objetos, é isso que ele faz (além de Ler e Excluir, o CRUD se traduz em Criar e Atualizar). Obviamente, você pode dividir o repositório ainda mais tendo um EmployeeUpdateRepository
e assim por diante, mas isso raramente é necessário e uma única implementação geralmente pode conter todas as operações CRUD.
Pergunta 1: Você acabou com uma Employee
classe simples que agora (entre outros atributos) terá id. Se o ID está preenchido ou vazio (ou null
) depende se o objeto já foi salvo. No entanto, um ID ainda é um atributo que a entidade possui e a responsabilidade da Employee
entidade é cuidar de seus atributos, portanto, cuidar de seu ID.
Se uma entidade possui ou não um ID, normalmente não importa até que você tente fazer alguma lógica de persistência nela. Conforme mencionado na resposta à pergunta 5, é responsabilidade do repositório detectar que você não está tentando salvar uma entidade que já foi salva ou tentando atualizar uma entidade sem um ID.
Nota importante
Esteja ciente de que, embora a separação de preocupações seja grande, na verdade, projetar uma camada de repositório funcional é um trabalho bastante tedioso e, na minha experiência, é um pouco mais difícil de acertar do que a abordagem de registro ativo. Mas você terá um design muito mais flexível e escalável, o que pode ser uma coisa boa.
Employee
objeto para fornecer abstração, as perguntas 4. e 5. geralmente não podem ser respondidas, dependem de suas necessidades e, se você separar a estrutura e as operações CRUD em duas classes, é bastante claro que o construtor doEmployee
não pode buscar dados do db anymore, para que responda 6. #