Aplicar princípios de Unit of Work no Apex
Objetivos de aprendizagem
Após concluir esta unidade, você estará apto a:
- Descrever a classe Unit of Work e seus métodos.
- Usar a classe fflib_SObjectUnitOfWork e sua API no Apex.
Acompanhar com o Trail Together
Deseja acompanhar um especialista enquanto trabalha nesta etapa? Veja este vídeo que faz parte da série Trail Together.
(Este clipe começa na marca dos 01:08:31 minutos, caso você queira retroceder e ver o início da etapa novamente.)
Como esticar as pernas com a Unit of Work
Já que se trata de conteúdo avançado, só faz sentido considerar um cenário mais complexo: criar uma oportunidade e todos os registros dependentes obrigatórios do zero. Isso é muito! O código de configuração de teste a seguir mostra como isso pode ser feito sem a Unit of Work. Bem detalhado, não é?
List<Opportunity> opps = new List<Opportunity>(); List<List<Product2>> productsByOpp = new List<List<Product2>>(); List<List<PricebookEntry>> pricebookEntriesByOpp = new List<List<PricebookEntry>>(); List<List<OpportunityLineItem>> oppLinesByOpp = new List<List<OpportunityLineItem>>(); for(Integer o=0; o<10; o++) { Opportunity opp = new Opportunity(); opp.Name = 'Opportunity ' + o; opp.StageName = 'Open'; opp.CloseDate = System.today(); opps.add(opp); List<Product2> products = new List<Product2>(); List<PricebookEntry> pricebookEntries = new List<PricebookEntry>(); List<OpportunityLineItem> oppLineItems = new List<OpportunityLineItem>(); for(Integer i=0; i<o+1; i++) { Product2 product = new Product2(); product.Name = opp.Name + ' : Product : ' + i; products.add(product); PricebookEntry pbe = new PricebookEntry(); pbe.UnitPrice = 10; pbe.IsActive = true; pbe.UseStandardPrice = false; pbe.Pricebook2Id = Test.getStandardPricebookId(); pricebookEntries.add(pbe); OpportunityLineItem oppLineItem = new OpportunityLineItem(); oppLineItem.Quantity = 1; oppLineItem.TotalPrice = 10; oppLineItems.add(oppLineItem); } productsByOpp.add(products); pricebookEntriesByOpp.add(pricebookEntries); oppLinesByOpp.add(oppLineItems); } // Insert Opportunities insert opps; // Insert Products List<Product2> allProducts = new List<Product2>(); for(List<Product2> products : productsByOpp) { allProducts.addAll(products); } insert allProducts; // Insert Pricebooks Integer oppIdx = 0; List<PricebookEntry> allPricebookEntries = new List<PricebookEntry>(); for(List<PriceBookEntry> pricebookEntries : pricebookEntriesByOpp) { List<Product2> products = productsByOpp[oppIdx++]; Integer lineIdx = 0; for(PricebookEntry pricebookEntry : pricebookEntries) { pricebookEntry.Product2Id = products[lineIdx++].Id; } allPricebookEntries.addAll(pricebookEntries); } insert allPricebookEntries; // Insert Opportunity Lines oppIdx = 0; List<OpportunityLineItem> allOppLineItems = new List<OpportunityLineItem>(); for(List<OpportunityLineItem> oppLines : oppLinesByOpp) { List<PricebookEntry> pricebookEntries = pricebookEntriesByOpp[oppIdx]; Integer lineIdx = 0; for(OpportunityLineItem oppLine : oppLines) { oppLine.OpportunityId = opps[oppIdx].Id; oppLine.PricebookEntryId = pricebookEntries[lineIdx++].Id; } allOppLineItems.addAll(oppLines); oppIdx++; } insert allOppLineItems;
Vamos refazer o código acima usando o padrão Unit of Work. Primeiro, comece criando uma instância de Unit of Work. Lembre-se de que os objetos transmitidos ao construtor devem estar na ordem de dependência para que o método commitWork possa inseri-los na ordem correta.
// Create a Unit Of Work fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork( new Schema.SObjectType[] { Product2.SObjectType, PricebookEntry.SObjectType, Opportunity.SObjectType, OpportunityLineItem.SObjectType } );
Na unidade anterior, o método de serviço applyDiscount usou o método registerDirty a fim de registrar os registros Oportunidade e Linha de oportunidade para atualização. O método registerNew insere novos registros. Ao inserir os registros filhos, esse método também faz com que a ID de pai correta seja aplicada antes de inserir os registros filhos. Você pode ver isso na prática na linha 22. Se você tem outros relacionamentos a criar, pode chamar o método registerRelationship (linha 21). Agora, vamos colocar nossa Unit of Work para funcionar! .
// Do some work! for(Integer o=0; o<10; o++) { Opportunity opp = new Opportunity(); opp.Name = 'UoW Test Name ' + o; opp.StageName = 'Open'; opp.CloseDate = System.today(); uow.registerNew(opp); for(Integer i=0; i<o+1; i++) { Product2 product = new Product2(); product.Name = opp.Name + ' : Product : ' + i; uow.registerNew(product); PricebookEntry pbe = new PricebookEntry(); pbe.UnitPrice = 10; pbe.IsActive = true; pbe.UseStandardPrice = false; pbe.Pricebook2Id = Test.getStandardPricebookId(); uow.registerNew(pbe, PricebookEntry.Product2Id, product); OpportunityLineItem oppLineItem = new OpportunityLineItem(); oppLineItem.Quantity = 1; oppLineItem.TotalPrice = 10; uow.registerRelationship(oppLineItem, OpportunityLineItem.PricebookEntryId, pbe); uow.registerNew(oppLineItem, OpportunityLineItem.OpportunityId, opp); } }
Conforme a unidade anterior, esses métodos não executam operações de bancos de dados. Para isso, você precisa confirmar o trabalho usando o método commitWork, que chama as instruções DML corretas com os registros que foram registrados na ordem correta de filho e, depois, pai. Além disso, no caso de relacionamentos, ele atribui a ID de pai aos registros filhos, costurando seu modelo de objeto conforme vai sendo inserido no banco de dados.
// Commit the work to the database! uow.commitWork();
Observe que o código não só é mais curto, mas bem mais fácil de ler em termos de lógica, sem todas as listas em volta! Vejamos mais exemplos dos métodos register que você pode usar.
// Inserts new Opportunity when committing uow.registerNew(opp); // Inserts new Opportunity Line Item and associates it with the given Opportunity record when committing uow.registerNew(oppLineItem, OpportunityLineItem.OpportunityId, opp); // Relates the given Opportunity Line Item to the given Price Book Entry when committing uow.registerRelationship(oppLineItem, OpportunityLineItem.PricebookEntryId, pbe);
Resumo
Neste módulo, você aprendeu sobre os benefícios de exibir sua implementação de aplicativo em camadas que têm suas próprias considerações e questões. Assim como qualquer organismo, cada uma tem seu papel em tornar seu aplicativo mais sólido e duradouro.
Você deu o primeiro passo isolando o coração do seu aplicativo, a lógica de negócios. Você pode parar por aqui ou continuar a aplicar questões de separação ao comportamento do objeto (código acionador) e a consultar informações necessárias ao aplicativo. Não deixe de conferir o módulo Padrões corporativos do Apex: camadas de domínio e seletor, que continua com as camadas de aplicativo de domínio e seletor. Por enquanto, divirta-se criando seus serviços!
Preparação para os desafios
Para concluir os desafios, você precisará usar algumas bibliotecas de código aberto. A classe fflib_SObjectUnitOfWork
é parte da biblioteca de código aberto Apex Common, que é dependente da biblioteca de código aberto ApexMocks Framework. Você precisará instalar o ApexMocks primeiro e, depois, o Apex Commons. Leia mais sobre essas bibliotecas e respectivos contratos de licença de código aberto em seus repositórios.
Para instalar as bibliotecas em sua organização, basta usar os botões “Implantar” abaixo.
Implantar a biblioteca de código aberto ApexMocks.
Implantar a biblioteca de código aberto Apex Common.
Recursos
- Separação de preocupações (Wikipedia)
- Padrões de arquitetura empresarial de Martin Fowler
- Padrões Unit of Work de Martin Fowler
- Padrões corporativos do Apex – repositório GitHub
- Como gerenciar sua DML e transações com uma Unit of Work
- Produzir mais com a Unit of Work