No meu tópico anterior, discutimos um pouco sobre quem deveria ser responsável por persistir a informação no banco. No livro do Nilsson, ele sugere algo do tipo:
A implementacao de adiciona() no caso seria
_session.Save(x);
ou
vocês repassam esse session pra um DAO e este sim executa o Save()? Pq aí eu não vejo muita vantagem em ter diversos DAOs, já que o repositorio acaba recebendo o ISession do NHibernate!
Ou vocês utilizam a estratégia do Service, igual o Sérgio explicou no post anterior?
DAO traz a abstração de em qual banco voce vai persistir, neste caso representado pela Session do hibernate (que pra ser honesto é um pouco diferente que um DAO, talvez um “DomainStore” versão “DataMapper” rs, mas admita por momento que ele seja seu DAO)… Na verdade, não é necessário “vários DAOs” (em relação a representação e não a infra), o que se precisa é de vários repositórios.
PS: o código fica melhor com o DAO injetado (do que simplesmente uma IoC manual pelo consrtrutor), pelo menos as dependencias não ficam evidentes no domínio.
O problema é que o ISession tem que ser injetado pelo construtor mesmo, pois eu posso ter diferentes Units of Work em diferentes repositórios. A idéia é de injetar o ISession (Unit Of Work) e depois deixar o UnitOfWork persistir todos os dados.
Minha dúvida é se vocês usam o próprio ISession do HHibernate como Unit of Work, ou vocês abstraem e criam seu próprio Unit of Work e implementam um NHibernateUnitOfWork…
Ou vocês no fundo, usam outra estratégia para persistir os dados, como Services, igual o Sérgio explicou! Essa estratégia do Unit of Work me agradou bastante, o código fica bem bacana!
Mas qual é a diferença de você injetar na mão ou um framework fazer a inversão pra você?
Eu prefiro um abstractDao do que usar diretamente a implementação do Hibernate, por exemplo. No implementar eu tenho liberdade de fugir do JPA e usar features do Hibernate ou preferir seguir a risca a especificação do JavaPersistence, essa é “uma” das vantagem de abstrair. Claro que para ter um efeito melhor, as queries tbm deveriam ser abstraídas por QueryObjects… embora de um trampo maior, suas dependencias ficam menores. Mas apenas abstrair os gerenciadores, já podem ser interessantes, olhando para o repository e não vendo nenhum EntityManager por exemplo.
OBS: Os repositórios neste modelo não seriam simples “delegadores”, eles conteriam as regras para a consulta. Tbm deixo claro que essa é UMA possível implementação, para cada caso pode ocorrer uma engenharia diferente para a resolução do problema motivado pelo padrão.
Nunca implementei realmente um framework de IoC, então me corrija se eu estiver errado: Com a injeção sendo feita de forma automática, eu não tenho a instância do objeto que foi injetado, a não ser que eu pegue através de um getUnitOfWork() ou algo assim, correto? E caso eu queira a mesma instância em 2 repositórios diferentes, como eu faria?
E como esse UnitOfWork é responsável pela minha persistência, como eu o chamaria? Através de um get() no repositório?
// rep1 e rep2 tem instancias diferentes do meu UoW, correto?
Repositorio1 rep1 = FrameworkDeIoC.cria(Repositorio1);
Repositorio2 rep2 = FrameworkDeIoC.cria(Repositorio2);
rep1.getUnitOfWork().persistAll(); // isso?
Isto é tipicamente o codigo dentro de um UnitOfWork.
O Session não é um UnitOfWork é um DAO. ( Na realiaade é uma parte do DomainStore que é o Hibernate… mas ok)
A tecnologia de persistencia ( aka a API de persistencia) tem que ser chamada do DAO. Caso contrário vc tem um vinculo forte (acoplamento) com essa API. Esse vinculo não é necessáriamente ruim. Vc tem que escolher se quer usar a API directamente e se ferrar depois, ou usar o DAO e não se ferrar depois. Mas se o sistema so for funcionar por 1 mes tlv esse “depois” nunca chegue e portanto a escolha é menos relevante.
A ideia é: lei de murphy : vc vai se ferrar de qualquer jeito: use DAO.
Faça UnitOfWork ser um objeto explicito com transação controladada fora dele. É mais fácil.
Pense no UnitOfWork como um Command cuja execução é especial: é dentro de uma transação e num ambiente que controla concurrencia.
Os Unit of Works de vocês tem algo a mais do que isso?
[/quote]
Esse é o padrão UnitOfWork tal como definido pelo Fowler. Na prática não encontro essa forma do padrão util.
Eu perfiro um versão um pouco diferente (que chamo de WorkUnit para não confundir).
abstract classs WorkUnit {
public abstract String getWorkResource();
public abstract void doLocks(WorkContext context);
public abstract void doWork(WorkContext context);
}
class WorkUnitExecutor{ // ponto de invocação dos trabalhos
public void execute (WorkUnit wu){
// faz queue da unidade conforme o recurso
WorkContext wc = new WorkContext (wu)
wc.execute();
}
}
class WorkContext {
@Inject DAO realDAO;
WorkDAO workDAO = new WorkDAO();
public void lockResource(String resource){
// faz o lock do recurso. controla locks anteriores.
// em caso de problema envia um OptimisticLockException.
}
execute(){
Transacion t= ...;
// joga workDAO no thread
try{
t.begin()
this.wu.doLocks(this);
this.wu.doWork(this);
workDAO.commitTO(daoReal);
t.commit();
} catch (){
t.rollback();
} finally {
releaseLocks();
// retira workDAO do thread
}
}
}
// uso
TransferWorkUnit wu = new TransferWorkUnit ( // parametros como contas, valores etc..);
WorkExecutor.getInstance().execute(wu);
class TransferWorkUnit extends WorkUnit {
public void doWork(WorkContext wc){
RepositoryA r = Repositories.getRepository(RepositoryA.class);
// faz algo com r
}
}
A ideia é que existe um objetivo do tipo comando (o WorkUnit) cuja execução é controlada através de um contexto. Ele pode usar esse contexto para fazer lock de recurso para controlar concorrencia ( pois a do banco não é suficiente já que pode nem existir um banco…).
O contexto usa um DAO especial. Este dao recebe todos as ações mas não as executa, apenas as coloca numa lista. Só quando acontece o commit é que o DAO joga os comandos para um DAO real.
Aqui é extremamente importante que o Repositorio possa ser injetado com o DAO. O repositorio é obtido através de um Registry que olha um threadLocal pelo DAO que está valendo. O WrokContext manipula esse threadlocal para que o Repositorio seja injetado com o DAO “dummy” . Dentro do codigo do workunit que é o que o programador escreve não ha diferença nenhuma já que ele está proibido de usar o DAO , tem que usar o respositório sempre que mexe com regras de negocio.
No fim tudo é revertido para a forma original.
Vc perguntou como eu fazia… :lol:
O meu WorkDAO seria o equivalente ao UnitOfWork do Fowler.
Repare que o UnitOfWork é um buffer do DAO , ou seja, as acções são iguais aos do DAO mas elas não acontecem realmente até que ha o commit final. Ele é um DAO com um método commit().
Nessa estrutura realmente o Repositorio tem que ser injetado com o DAO certo, especialmente em operação que manipulam os dados dentro de uma transação. E o UnitOfWork tem que ser injetado com o DAO real
Não faz sentido o Repositorio ter um UnitOfWork porque o UnitOfWork é opcional. Num select vc não usa UoW.
A ideia seria mais ou menos assim :
class MyService {
@Inject DAO daoReal
public void algumServiço(){
try{
UoWDAO dao = new UoWDAO(daoReal);
Repositorio r = new Repositorio(dao);
// faz algo com r
dao.commit();
} catch (){
// não faz dao.commit o que é equivalente a roolback
}
}
}
Nota: a annotaçao @Inject é só para dizer que deve ser injetado. Não é uma especificação de como a injeção deve acontecer. Básicamente significa “obtenha isto de alguma forma”
Nesse seu último exemplo, você passando UoWDAO pro repositório, essa interface deve conter também os métodos do DAO, de listagem, e tudo mais, concorda?
Estou com um projeto aqui chamado SmartCA, não me lembro da onde eu baixei, mas é um exemplo de DDD em .NET. Olha a abordagem dele, e me diga o que você acha.
public interface IUnitOfWorkRepository
{
void PersistNewItem(IEntity item);
void PersistUpdatedItem(IEntity item);
void PersistDeletedItem(IEntity item);
}
public class Repository : OutrasInterfaces, IUnitOfWorkRepository {
private IUnitOfWork unitOfWork;
// aqui os metodos add() , remove() apenas chamam as funcoes do IUnitOfWork
// e implementa os metodos de IUnitOfWorkRepository que realmente persistem os dados no banco.
// aqui tb tem funcoes de listagem como FindBy() e etc...
public void Add(T item)
{
if (this.unitOfWork != null)
{
this.unitOfWork.RegisterAdded(item, this);
}
}
}
public interface IUnitOfWork
{
void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository);
void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository);
void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository);
void Commit();
object Key { get; }
IClientTransactionRepository ClientTransactionRepository { get; }
}
public class UnitOfWork : IUnitOfWork {
// aqui os metodos guardam as classes em colecoes, e no Commit(), elas passam a IEntity
// para os IUnitOfWorkRepository, esses que realmente executam a persistencia
// segue codigo do commit() para melhor entendimento
public void Commit()
{
using (TransactionScope scope = new TransactionScope())
{
foreach (IEntity entity in this.deletedEntities.Keys)
{
this.deletedEntities[entity].PersistDeletedItem(entity);
this.clientTransactionRepository.Add(
new ClientTransaction(this.key,
TransactionType.Delete, entity));
}
foreach (IEntity entity in this.addedEntities.Keys)
{
this.addedEntities[entity].PersistNewItem(entity);
this.clientTransactionRepository.Add(
new ClientTransaction(this.key,
TransactionType.Insert, entity));
}
foreach (IEntity entity in this.changedEntities.Keys)
{
this.changedEntities[entity].PersistUpdatedItem(entity);
this.clientTransactionRepository.Add(
new ClientTransaction(this.key,
TransactionType.Update, entity));
}
scope.Complete();
}
this.deletedEntities.Clear();
this.addedEntities.Clear();
this.changedEntities.Clear();
this.key = Guid.NewGuid();
}
}
Conseguiu entender? Essa implementação também é bastante interessante, não?
Nesse seu último exemplo, você passando UoWDAO pro repositório, essa interface deve conter também os métodos do DAO, de listagem, e tudo mais, concorda?
[/quote]
sim esqueci de dizer que UoWDAO implementa DAO
É isso ai. Só achei estranho esta parte :
public class Repository : OutrasInterfaces, IUnitOfWorkRepository {
private IUnitOfWork unitOfWork;
// aqui os metodos add() , remove() apenas chamam as funcoes do IUnitOfWork
// e implementa os metodos de IUnitOfWorkRepository que realmente persistem os dados no banco.
// aqui tb tem funcoes de listagem como FindBy() e etc...
public void Add(T item)
{
if (this.unitOfWork != null)
{
this.unitOfWork.RegisterAdded(item, this);
}
}
}
Se o unitOfWork é nulo o repositorio não faz nada ? … meio estranho.
Por outro lado, quem chama o método Commit ? Não é o repositorio , certo ?
Logo o repositorio não precisa saber que existe um UoW , apenas o DAO. E o DAO é obrigatorio existir ou dá exception.
É que nesse exemplo em .NET não ha DAO e o UoW faz o papel tanto de UnitOfWork como de DAO.
Eu só acho que é melhor separar as resposabilidades. Caso contrário eu poderia escrever este codigo sem sentido
public class Repository : OutrasInterfaces, IUnitOfWorkRepository {
private IUnitOfWork unitOfWork;
// aqui os metodos add() , remove() apenas chamam as funcoes do IUnitOfWork
// e implementa os metodos de IUnitOfWorkRepository que realmente persistem os dados no banco.
// aqui tb tem funcoes de listagem como FindBy() e etc...
public void Add(T item)
{
if (this.unitOfWork != null)
{
this.unitOfWork.RegisterAdded(item, this);
this.unitOfWork.commit(); // !!! Já estraguei a transação
}
}
}
Essa discussão tá ficando interessante e me ajudando demais!
Só não entendi uma coisa, você falou que tem que separar as responsabilidades, mas você disse que UoWDAO implementa DAO, ou seja, UoW é um Unit of Work e também é um DAO? Você passa o DAO real (que foi injetado) para o UoW… Não fica meio estranho você ter o DAO na camada de aplicação?
Não é tão casual assim separar as responsabilidades desses dois, né?!
Essa discussão tá ficando interessante e me ajudando demais!
Só não entendi uma coisa, você falou que tem que separar as responsabilidades, mas você disse que UoWDAO implementa DAO, ou seja, UoW é um Unit of Work e também é um DAO?
[/quote]
ah! mas um DAO não é um UnitOfWork. Essa é a separação. Um Unit of Work tem mais responsabilidades. Ele tem que implementar commit(); É essa responsabilidade que o DAO não tem, nem deve ter.
Eu não o “tenho”. E ele não está em lado nenhum. Eu apenas preciso de o injetar.
Repare que o uso do UoW como um DAO é um simplicifador. Vc é livre de criar o UoW com a interface que quiser. Contudo, em algum ponto, o DAO real será chamado. Então porque não dentro do commit() ?
Não entendi o que quer dizer com esta frase. Separação de responsabilidade é o dia-a-dia. É o que separa sistemas bem feitos de gambiarras…
Então vamos ver se eu entendi… O Unit Of Work vai receber um DAO, meu Repositorio vai receber meu UoW. Meu UoW então vai ter que disponibilizar toda a funcionalidade do DAO para o repositorio… Imagine o seguinte caso… Tenho 2 repositorios… Clientes e Pedidos…
class RepositorioPedidos {
IUnitOfWork _uow; // injetado no construtor
Pedido PegaPorId(int id);
adiciona(Pedido p);
altera(Pedido p);
remove(Pedido p);
}
class RepositorioClientes {
IUnitOfWork _uow; // injetado no construtor
List<Cliente> PegaTodos();
adiciona(Cliente p);
altera(Cliente p);
remove(Cliente p);
}
Repare que eles tem em comum apenas os métodos de persistência, mas na listagem cada um tem o seu. Vc sugere meu IUnitOfWork ter métodos do tipo FindBy(QueryObject qo), FindByPk(), e coisas do tipo ?
public interface IUnitOfWork {
void adiciona(object o);
void altera(object o);
void remove(object o);
void commit();
T findByPk(T obj);
}
Então vamos ver se eu entendi… O Unit Of Work vai receber um DAO, meu Repositorio vai receber meu UoW. Meu UoW então vai ter que disponibilizar toda a funcionalidade do DAO para o repositorio… Imagine o seguinte caso… Tenho 2 repositorios… Clientes e Pedidos…
[/quote]
Não. O Unit Of Work vai receber um DAO, meu Repositorio vai receber um DAO, que pode ser ou não um UoW. Meu UoW então vai ter que disponibilizar toda a funcionalidade do DAO para o repositorio de forma “bufferizada”… e o método commit.
O ponto é que o repositorio não pode saber da existencia do UoW, mas ele tem que usá-lo como se fosse um DAO normal.
Sim. Na realidade vc só precisa de um método desses FindBy(QueryObject qo). findbyPk é um caso particular de FindBy(QueryObject qo).
Porquê eu preciso disso ? Porque durante a execução do trabalho vou ter que fazer pesquisas. Essas pesquisas têm tb que ser executadas. O UoW sendo um DAO tem que as fazer. O detalhe é como ele implementa isso. A principio ele simplesmente delega ao DAO real. Contudo em alguns casos vc quer ter um sistema misto, ou seja,
quando vc faz o query vc quer trazer dados no dao real e dados que vc incluio previamente no trabalho. Algo assim
Repositorio r = ...
r.insere(item);
QueryObject qo = QueryObject.allItens();
List<Item> all = r.find(qo);
a pesquisa já inclui o item inserido com r.insere().
Isto é contornável e muitas vezes a delegação directa é suficiente.
por exemplo
Repositorio r = ...
r.insere(item);
QueryObject qo = QueryObject.allItens();
List<Item> all = r.find(qo); // não tras o adicionado antes
all.add(item); // agora sim
A única coisa que um unit of Work faz é listar os objetos afetados por uma transação para que eles sejam atualizados no banco de dados. Você não deve se preocupar em implementar um Unit of Work relacionao dà persistência, é exatamente para isso você usa um ORM como hibernate:
O seu primeiro exemplo já faz uso de Unit of Work provida pelo hibernate, a única coisa aconselhável a alterar é, como o Alessandro falou, ter as dependências injetadas e demarcar transações ao invés de fazer controle manual.
Quanto à chamar ou não um DAO, se você modela seu Repositório como uma interface ao invés de uma classe basta fazer o DAO (que chama o save() na Session/UoW) implementá-lo.
[quote=dreampeppers99]E porque não criar três métodos para os repositórios. (baseado na idéia que os Dao’s vão compor o Repository)
3 métodos:
inicia()
finaliza()
reverte()
[/quote]
Porque o repositorio não é responsável por controlar transações. Aliás o DAO também não é. Dai a necessidade de um outro objeto para fazer isso. O UnitOfWork é um objeto que ajuda a fazer isso quando existe uma necessidade de controlar concurrencia além de transação e/ou quando o DAO não ha outros mecanismo de transação.
Em ambiente JEE provavelmente vc escreve um serviço anotado com @Transaction e pronto. Mas isso nem sempre é suficiente e fora do ambiente JEE o controle tem que ser mais fino. Contudo, o controle não pode ser adicionado nos objetos que vc já tem pois isso iria sobre-carregar os objetos com responsabilidades que não lhes competem.