Skip to main content

Conhecer os princípios de Unit of Work

Objetivos de aprendizagem

Após concluir esta unidade, você estará apto a:

  • Gerenciar eficazmente suas operações DML e evitar atualizações parciais de banco de dados.
  • Entender os recursos e as vantagens da implementação do Apex do padrão.
  • Aplicar o padrão Unit of Work ao método de serviço applyDiscount da unidade anterior.

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 54:05 minutos, caso você queira retroceder e ver o início da etapa novamente.)

Princípios da Unit of Work

Unit of Work é um padrão de design que reduz código repetitivo ao implementar gerenciamento de transações e as sobrecargas de códigos de adesão à massificação da DML por meio do uso extensivo de mapas e listas. Não é um requisito para implementar uma camada de serviço, mas pode ajudar. Vamos mostrar a você um exemplo de antes e depois para explicar como isso funciona.

O padrão Unit of Work usado neste módulo está baseado no padrão descrito por Martin Fowler: “Mantém uma lista de objetos afetada por uma transação de negócios e coordena a escrita de alterações e a resolução de problemas de simultaneidade.”

Na plataforma Force.com, isso quer dizer que o padrão lida com os seguintes casos de uso:

  • Registrar atualizações de registros, inserções e exclusões para implementar um requisito de negócios específico
  • Registrar relacionamentos de registro para facilitar a inserção de registros filhos ou registros relacionados com menos codificação
  • Quando solicitado para escrever (ou confirmar) no banco de dados, massifica todos os registros capturados
  • Fazer wrapping de DML em SavePoint, liberando o desenvolvedor de implementar isso todas as vezes para cada método de serviço escrito.

Como implementar um método de serviço sem uma Unit of Work

Para entender melhor o que o padrão Unit of Work tem para oferecer, vamos primeiro analisar o código que precisamos escrever sem o padrão Unit of Work em cada método de serviço, aderindo mesmo assim às melhores práticas de design discutidas anteriormente. Precisamos escrever código para um requisito de negócios específico, mas também tê-lo como código clichê para implementar o seguinte:

  • Massificação e otimização de DML – O código pode atualizar alguns ou todos os registros Oportunity ou OpportunityLineItem, dependendo do fluxo de lógica. Ele cria e popula duas listas para manter apenas a leitura de registros que precisam ser atualizados.
  • Tratamento de erros e gerenciamento de transação – Segundo os princípios de design da camada de serviço, deve confirmar todas as alterações ou nenhuma se ocorrer um erro, independentemente se o chamador capturar quaisquer exceções geradas. Lembre-se que a plataforma reverte automaticamente apenas se as exceções não forem tratadas, o que não é desejável desde uma perspectiva de exceção de usuário. É uma boa prática para o código da camada de serviço gerenciar um escopo de transação usando o recurso SavePoint e a semântica try/catch.

O exemplo a seguir usa um SavePoint para encapsular e fazer wrapping das operações de banco de dados em um método de serviço. Conforme as considerações de design, o SavePoint é usado para evitar que o chamador capture exceções (talvez resultantes da segunda instrução DML). Isso resultaria então no tempo de execução do Apex confirmando atualizações para as linhas de oportunidade (primeira instrução DML), causando uma atualização parcial no banco de dados.

public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) {
    // Validate parameters
    // ...    // Query Opportunities and Lines
    // ...    // Update Opportunities and Lines (if present)
    List<Opportunity> oppsToUpdate = new List<Opportunity>();
    List<OpportunityLineItem> oppLinesToUpdate = new List<OpportunityLineItem>();
    // Do some work...    Decimal factor = 1 - (discountPercentage==null ?0 :discountPercentage / 100);
    for(Opportunity opportunity :opportunities) {
        // Apply to Opportunity Amount
        if(opportunity.OpportunityLineItems!=null && opportunity.OpportunityLineItems.size()>0) {
            for(OpportunityLineItem oppLineItem :opportunity.OpportunityLineItems) {
                oppLineItem.UnitPrice = oppLineItem.UnitPrice * factor;
            oppLinesToUpdate.add(oppLineItem);
            }
        } else {
            opportunity.Amount = opportunity.Amount * factor;
          oppsToUpdate.add(opportunity);
        }
    }
    // Update the database
    SavePoint sp = Database.setSavePoint();
    try {
      update oppLinesToUpdate;
      update oppsToUpdate;
    } catch (Exception e) {
      // Rollback
      Database.rollback(sp);
      // Throw exception on to caller
      throw e;
    }
}

Implementação do Apex do padrão Unit of Work

O restante desta unidade faz referência a uma biblioteca de código aberto do Apex que contém uma implementação do padrão Unit of Work de Martin Fowler. É implementado por meio de uma única classe, fflib_SObjectUnitOfWork, então abra isso em uma outra guia. Na próxima unidade, iremos trabalhar com esta classe mas, por enquanto, vamos entender um pouco mais sobre seus métodos-chave.

Esta classe expõe métodos para permitir que uma instância da classe fflib_SObjectUnitOfWork capture registros que precisam ser criados, atualizados ou excluídos conforme o código de serviço é executado por meio de métodos de registro. Além disso, o método commitWork encapsula o SavePoint e a convenção try/catch.

A atualização do banco de dados com DML só ocorre quando o método commitWork é chamado. Então, o código de serviço pode chamar os métodos de registro sempre que for preciso, mesmo em loops. Essa abordagem permite que o desenvolvedor se concentre na lógica de negócios e não no código para gerenciar várias listas e mapas.

Por fim, como mostrado no diagrama a seguir, o escopo da Unit of Work é determinado pelo início e fim do seu código de método de serviço. Somente chame o método commitWork quando no escopo do método de serviço.

Para incluir a Unit of Work em seus métodos de código de serviço, siga estas etapas.

  1. Inicialize uma Unit of Work e use-a para definir o escopo de todo o trabalho pelo método de serviço.
  2. Garanta que a lógica da camada de serviço registre registros com a Unit of Work quando é executada.
  3. Chame o método commitWork da Unit of Work para massificar e executar o DML.

O diagrama a seguir mostra as etapas acima e reforça o escopo de cada etapa com relação à execução do código do método de serviço.

Como incluir Unit of Work: crie uma nova instância de Unit of Work, faça o trabalho, registre as alterações do registro e finalmente confirme uma Unit of Work massificada no banco de dados.

Nota

Nota

Se você estiver chamando entre serviços, transmita a instância de Unit of Work externa como um parâmetro por meio da sobrecarga do método. Não crie uma nova. Uma vez que a Unit of Work está representando o escopo transacional do método de serviço, tenha em conta apenas uma instância de Unit of Work por chamada de método, conforme mostrado abaixo.

Para usar a classe Unit of Work, você precisa construí-la com uma lista dos objetos com que seu código está interagindo. Os objetos precisam estar em ordem de dependência para garantir que os registros pai e filho registrados sejam inseridos pelo método commitWork na ordem correta. Vamos explorar mais sobre como lidar com o relacionamento pai-filho de fflib_SObjectUnitWork na próxima unidade.

fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
        new List<SObjectType> { OpportunityLineItem.SObjectType, Opportunity.SObjectType  }
);

Como implementar um método de serviço com a Unit of Work

O exemplo a seguir aplica o padrão Unit of Work ao serviço que criamos na unidade anterior. O código que não mudou não é mostrado. Observe que as listas desapareceram e que o SavePoint não tem o código clichê try/catch em seu redor porque é tudo tratado pela classe fflib_SObjectUnitOfWork

public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) {
    // Unit of Work
    fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork(
        new List<SObjectType> { OpportunityLineItem.SObjectType, Opportunity.SObjectType  }
    );
    // Validate parameters
    // ...    // Query Opportunities and Lines
    // ...    // Update Opportunities and Lines (if present)
    // ...    for(Opportunity opportunity :opportunities) {
        // Apply to Opportunity Amount
        if(opportunity.OpportunityLineItems!=null && opportunity.OpportunityLineItems.size()>0) {
            for(OpportunityLineItem oppLineItem :opportunity.OpportunityLineItems) {
                oppLineItem.UnitPrice = oppLineItem.UnitPrice * factor;
                uow.registerDirty(oppLineItem);
            }
        } else {
            opportunity.Amount = opportunity.Amount * factor;
            uow.registerDirty(opportunity);
        }
    }
    // Commit Unit of Work
    uow.commitWork();
}

A classe fflib_SObjectUnitOfWork agrega operações DML e faz o wrapping das mesmas em um SavePoint quando o método commitWork é chamado.

Em códigos mais complexos, com várias profundidades e classes, você pode optar por transmitir SObjectUnitOfWork (ou usar um estático). O código chamado pode continuar a registrar suas próprias atualizações de banco de dados, sabendo que o proprietário da Unit of Work, neste caso a camada de serviço, realiza uma única fase de confirmação ou reversão em seu nome.

Recursos

Continue a aprender de graça!
Inscreva-se em uma conta para continuar.
O que você ganha com isso?
  • Receba recomendações personalizadas para suas metas de carreira
  • Pratique suas habilidades com desafios práticos e testes
  • Monitore e compartilhe seu progresso com os empregadores
  • Conecte-se a orientação e oportunidades de carreira