Aplicar principios de la unidad de trabajo en Apex
Objetivos de aprendizaje
Después de completar esta unidad, podrá:
- Describir la clase Unidad de trabajo y sus métodos.
- Utilizar la clase fflib_SObjectUnitOfWork y su API en Apex.
Siga el proceso con Trail Together
¿Desea seguir el proceso con un experto a medida que realiza este paso? Mire este video que forma parte de la serie Trail Together.
(Este video comienza en el minuto 1:08:31, en caso de que desee rebobinar y mirar el comienzo del paso nuevamente).
Estiramiento de nuestras piernas con la Unidad de trabajo
Como este es un contenido avanzado, solo queda echar un vistazo a un escenario más complejo: la creación de una Oportunidad y todos los registros dependientes requeridos desde cero, cosa bastante sorprendente. El siguiente código de configuración de prueba muestra cómo se podría hacer esto sin la Unidad de trabajo. Más bien prolijo, ¿verdad?
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;
Ahora, rehagamos el código anterior utilizando el patrón Unidad de trabajo. Primero, comience creando una instancia Unidad de trabajo. Recuerde que los objetos pasados al constructor deben estar en orden de dependencia para el método commitWork para insertarlos en el orden correcto.
// Create a Unit Of Work fflib_SObjectUnitOfWork uow = new fflib_SObjectUnitOfWork( new Schema.SObjectType[] { Product2.SObjectType, PricebookEntry.SObjectType, Opportunity.SObjectType, OpportunityLineItem.SObjectType } );
En la unidad previa, el método de servicio applyDiscount utilizó el método registerDirty para registrar los registros Oportunidad y Partida de oportunidad para actualizar. El método registerNew inserta nuevos registros. Al insertar registros secundarios, este método también garantiza que se aplique el Id. principal correcto antes de insertar los registros secundarios. Puede ver esto en acción en la línea 22. Si tiene otras relaciones, también puede llamar el método registerRelationship (línea 21). ¿Ahora pongamos nuestra Unidad de trabajo en marcha! Nunca mejor dicho.
// 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); } }
Según la unidad anterior, estos métodos no realizan operaciones de base de datos. Para hacer eso, debe asignar el trabajo utilizando el método commitWork, que llama las declaraciones DML correctas con los registros que se registraron en el orden correcto de secundario y luego principal. Además, para relaciones, asigna el Id. principal en los registros secundarios, uniendo de forma efectiva su modelo de objeto cuando se inserta en la base de datos.
// Commit the work to the database! uow.commitWork();
Observe que el código es no solo más corto, también más fácil de leer en términos de lo que está ocurriendo sin todas esas fastidiosas listas alrededor. Aquí le mostramos más ejemplos de los métodos de registro que puede utilizar.
// 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);
Resumen
En este módulo, obtuvo información acerca de las ventajas de visualizar su implementación de aplicación en capas que tienen sus propias consideraciones e inconvenientes. Como cualquier organismo, cada uno desempeña su parte en hacer que su aplicación sea sólida y duradera.
Realizó el primer paso en esa ruta aislando el corazón de su aplicación, su lógica de negocio. Puede detenerse aquí o puede continuar para aplicar preocupaciones de división al comportamiento de objeto (código de desencadenador) y consultando información que su aplicación necesita. Asegúrese de consultar el módulo Capas de dominios y selectores de los Patrones de negocio de Apex, que amplía el tema de las capas de dominios y selectores en la aplicación. Mientras tanto, ¡disfrute creando sus servicios!
Preparación para los retos
Para completar estos retos, necesita implementar algunas bibliotecas de código abierto. La clase fflib_SObjectUnitOfWork
es parte de la biblioteca de código abierto Apex Common que es dependiente de la biblioteca de código abierto ApexMocks Framework. Deberá instalar ApexMocks primero y luego Apex Commons. Puede leer más acerca de ambas bibliotecas y sus acuerdos de licencia de código abierto respectivos en sus repositorios.
Para instalar las bibliotecas en su organización, simplemente utilice los botones "Implementar" a continuación.
Implemente la biblioteca de código abierto ApexMocks.
Implemente la biblioteca de código abierto Apex Commons.
Recursos
- Separation of Concerns (Wikipedia)
- Martin Fowler’s Enterprise Architecture Patterns
- Martin Fowler’s Unit of Work Patterns
- Patrones de negocio de Apex - Repositorio GitHub
- Gestión de su DML y transacciones con una unidad de trabajo
- Hacer más con la unidad de trabajo