Não se preocupe com o princípio de responsabilidade única. Isso não ajudará você a tomar uma boa decisão aqui, porque você pode escolher subjetivamente um conceito específico como "responsabilidade". Você poderia dizer que a responsabilidade da classe é gerenciar a persistência de dados no banco de dados ou que a responsabilidade é executar todo o trabalho relacionado à criação de um usuário. Esses são apenas níveis diferentes do comportamento do aplicativo e são expressões conceituais válidas de uma "responsabilidade única". Portanto, este princípio é inútil para resolver seu problema.
O princípio mais útil a ser aplicado nesse caso é o princípio da menor surpresa . Então, vamos fazer a pergunta: é surpreendente que um repositório com a função principal de persistir dados em um banco de dados também envie e-mails?
Sim, é muito surpreendente. Esses são dois sistemas externos completamente separados, e o nome SaveChanges
não implica também no envio de notificações. O fato de você delegar isso a um evento torna o comportamento ainda mais surpreendente, pois alguém que lê o código não consegue mais ver facilmente quais comportamentos adicionais são chamados. O indireto prejudica a legibilidade. Às vezes, os benefícios valem os custos de legibilidade, mas não quando você invoca automaticamente um sistema externo adicional que tem efeitos observáveis para os usuários finais. (O log pode ser excluído aqui, pois seu efeito é essencialmente a manutenção de registros para fins de depuração. Os usuários finais não consomem o log, portanto, não há nenhum dano em sempre o log.) Pior ainda, isso reduz a flexibilidade no tempo de enviar o email, tornando impossível intercalar outras operações entre o salvamento e a notificação.
Se seu código normalmente precisar enviar uma notificação quando um usuário for criado com sucesso, você poderá criar um método que faça isso:
public void AddUserAndNotify(IUserRepository repo, IEmailNotification notifier, MyUser user)
{
repo.Add(user);
repo.SaveChanges();
notifier.SendUserCreatedNotification(user);
}
Mas se isso agrega valor depende das especificidades do seu aplicativo.
Na verdade, eu desencorajaria a existência do SaveChanges
método. Esse método presumivelmente confirmará uma transação de banco de dados, mas outros repositórios podem ter modificado o banco de dados na mesma transação . O fato de confirmar todos eles é novamente surpreendente, pois SaveChanges
está especificamente vinculado a essa instância do repositório do usuário.
O padrão mais direto para gerenciar uma transação de banco de dados é um using
bloco externo :
using (DataContext context = new DataContext())
{
_userRepository.Add(context, user);
context.SaveChanges();
notifier.SendUserCreatedNotification(user);
}
Isso dá ao programador controle explícito sobre quando as alterações em todos os repositórios são salvas, força o código a documentar explicitamente a sequência de eventos que devem ocorrer antes de uma confirmação, garante que uma reversão seja emitida por erro (supondo que isso ocorra DataContext.Dispose
) e evita ocultos conexões entre classes com estado.
Também prefiro não enviar o email diretamente na solicitação. Seria mais robusto registrar a necessidade de uma notificação em uma fila. Isso permitiria um melhor tratamento de falhas. Em particular, se ocorrer um erro ao enviar o email, ele poderá ser tentado novamente mais tarde, sem interromper o salvamento do usuário, e evitará o caso em que o usuário foi criado, mas um erro é retornado pelo site.
using (DataContext context = new DataContext())
{
_userRepository.Add(context, user);
_emailNotificationQueue.AddUserCreateNotification(user);
_emailNotificationQueue.Commit();
context.SaveChanges();
}
É melhor confirmar a fila de notificação primeiro, pois o consumidor da fila pode verificar se o usuário existe antes de enviar o email, caso a context.SaveChanges()
chamada falhe. (Caso contrário, você precisará de uma estratégia de consolidação em duas fases completa para evitar erros de erro.)
A linha inferior é para ser prático. Na verdade, pense nas consequências (tanto em termos de risco quanto de benefício) de escrever código de uma maneira específica. Acho que o "princípio da responsabilidade única" não costuma me ajudar a fazer isso, enquanto o "princípio da menor surpresa" geralmente me ajuda a entrar na cabeça de outro desenvolvedor (por assim dizer) e pensar no que pode acontecer.