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.