by Andrew Fawcett

In the previous article, the Service Layer was discussed as a means to encapsulate your application’s programmatic processes. Focusing on how services are exposed in a consistent, meaningful and supportive way to other parts your application, such as Visualforce Controllers, Batch Apex and also public facing API’s you provide. This next article will deal with a layer in your application known as the Domain Layer.

Domain (software engineering)."a set of common requirements, terminology, and functionality for any software program constructed to solve a problem in that field”

With Force.com this is defined through the Custom Objects you create (Project, Task, Invoice etc.) allowing you to rapidly build the data storage of your application. But what about the behaviour of those objects? Validation, calculation and complex manipulation of this data. Behaviour on Force.com can be expressed both declaratively (point-and-click) and/or programmatically in Apex. Using this combination effectively is key to being a successful application developer on the platform.

If the complexity of certain behaviours relating to objects within your application is such that you require Apex coding, you may want to consider structuring and factoring such code using Object Oriented Programming (or OOP) techniques and the Domain Model pattern.

Domain Model, “An object model of the domain that incorporates both behavior and data.”, “At its worst business logic can be very complex. Rules and logic describe many different cases and slants of behavior, and it's this complexity that objects were designed to work with...” Martin Fowler, EAA Patterns

Like the Service layer, this is providing a further, but more granular, level of code encapsulation and reuse within your application, such as complex validation, defaulting and other logic relating to complex calculation and manipulation. This article aims to provide a Force.com perspective on the above pattern, providing guidelines as to how you can clearly associate Domain layer code to each of your Custom Objects. Enabling you to further manage effective layering and separation of concerns within your application code base and the benefits that brings.

Contents

Who uses the Domain layer?

There are two ways in which “Domain layer logic” is invoked.

  • Database Manipulation. CRUD operations, or more specifically Create, Update and Delete operations, occur on your Custom Objects as users or tools interact via the standard Salesforce UI or via one of the platform’s API’s. These can be routed to the appropriate Domain class code corresponding to that object and operation.
  • Service Operations. The Service layer implementations should be easily able to identify and reuse code relating to one or more of the objects each of its operations interact with via Domain classes. This also helps keeps the code in the service layer focused on orchestrating whatever business process or task it is exposing.

This diagram brings together the Service Layer and Domain Layer patterns and how they interact with each other as well other aspects of the application architecture.

AEP figure0.jpg

Important Note: Remember that code relating to Visualforce Controllers, Batch Apex, API classes etc you develop is encouraged to use functionality solely exposed via the Service layer operations. Thus code written in the Domain layer, is always indirectly invoked, and thus is typically an internal business logic aspect of your application.

Design Considerations

Essentially the Domain layer opens up Object Oriented Programming capabilities within Apex to your application but does so in a way that is aligned (mostly by naming conventions) with the ‘domain’ terms and concepts of your application.

  • Bulkification. As with the Service layer, this is a key design consideration on Force.com and has bearing on this layer’s design guidelines as well, ensuring that domain class constructors, parameters, methods and properties deal with data in terms of lists and not singular instances. This helps promote, and thus propagate further down the layers, this good practice more visibly. After all, there is no point in having a bulkified service if the implementation does not support it.
  • Extension by Containment. As discussed above domain logic must encapsulate both data and behaviour. Apex represents data as SObject’s. These cannot be extended, but can be wrapped or contained by another class, which can complement this data with appropriate behavioural code (methods, properties etc), Much like a StandardSetController contains the records being operated on in Visualforce.
  • Naming Conventions. Since the domain data is going to be a data set or list, as per the above bulkification convention, then so should your domain class ideally reflect this in its name, e.g. public class Invoices
  • Object Oriented Programming. In Force.com, one Custom Object cannot extend another in the same way classes do in OO programming languages such as Apex, so you can often end up creating a number of Custom Objects that have share similar aspects, sets of fields or relationships. When defining your Domain classes for such objects, consider using inheritance or interfaces, to abstract common behavioural aspects into a shared base class or interface, allowing you to create and reuse logic that applies across such objects. This article uses some aspect of OO programming. You should ideally ensure you are familiar with some of the basics such as inheritance and method overriding.
  • Separation of Concerns. Be consistent regarding where common types of domain logic such as validation and defaulting logic goes. While it is possible to mix this in with other logic related to Trigger events for example, it can prove more optimal to have this code executed in sequence, such as defaulting, validation then other logic. It may also be useful to have this code accessible in contexts not driven by database operations, such as services that provide validation or defaulting only behaviour.

Using Domain Classes

Here the services introduced in the previous article will be extended. Previously the domain logic was inlined into the service layer methods. The following code shows a new implementation of the service method applyDiscounts. This time using a domain class that encapsulates the logic and how to delegate to the domain class to invoke it.


global static void applyDiscounts(Set<ID> opportunityIds, Decimal discountPercentage)
{
	// Create unit of work to capture work and commit it under one transaction
	SObjectUnitOfWork uow = new SObjectUnitOfWork(SERVICE_SOBJECTS);
	
	// Query Opportunities and construct domain class
	Opportunities opportunities = 
		new Opportunities([select ... from Opportunity where Id in :opportunityIds]);

	// Apply discount via domain class behaviour
	opportunities.applyDiscount(discountPercentage, uow);
	
	// Commit updates to opportunities		
	uow.commitWork();						
}

Trigger Handling

Before getting into the implementation of the Opportunities domain class used above, we must also consider another entry point into this logic, as illustrated in the diagram above, that being Apex Triggers. Given the role of the domain class, in respect to encapsulating all logic and behaviour for a given object, it is important to ensure Apex Trigger events are also routed to the appropriate domain class methods.

As with many emerging patterns around Apex Triggers it is good to keep the logic minimal. Apex Triggers are not classes and have no means to split logic into methods or inherit from a base class, so make poor containers for even moderately complexity logic. The following Apex Trigger is possible given the use of the SObjectDomain class. This class is provided as part of the Apex code accompanying this article. The static method triggerHandler ensures that the correct logic in the domain class is invoked based on the state of the Trigger state variables, isAfter, isBefore, isInsert etc...

trigger OpportunitiesTrigger on Opportunity (after delete, after insert, after update, 
  before delete, before insert, before update) 
{
   // Creates Domain class instance and calls appropriate methods
   SObjectDomain.triggerHandler(Opportunities.class);
}

Creating Domain Classes

The SObjectDomain class used in the trigger code above, is actually a base class for all domain classes, providing some useful functionality, such as object security.

AEP figure1.jpg

It utilises the template method pattern in its design, to provide standard hooks to implement common domain logic, for validation onValidate and defaulting onApplyDefaults. These are provided as virtual methods, so overriding them is optional, you may still choose to place this logic in the Trigger methods below. The handleXXXX methods in this base class are responsible for ensuring such virtual methods are called at the appropriate times.

In addition, are methods to place logic relating to Apex Trigger events, onBeforeInsert, onBeforeUpdate, onBeforeDelete and of course onAfterInsert, onAfterUpdate and onAfterDelete. The constructor (for which all classes that extended it must also expose) takes a list of SObject’s as per the bulkification design goal described above.

This is a basic implementation of the Opportunities domain class.

public with sharing class Opportunities extends SObjectDomain
{
	public Opportunities(List<Opportunity> sObjectList)
	{
		super(sObjectList);
	}
	
	public class Constructor implements SObjectDomain.IConstructable
	{
		public SObjectDomain construct(List<SObject> sObjectList)
		{
			return new Opportunities(sObjectList);
		}
	}
}


Note: The Constructor inner class allows the base class method SObjectDomain.triggerHandler used in the Apex Trigger sample above, to create a new instance of the domain object passing in the SObject list (e.g Trigger.new). Once Apex fully supports reflection, this will not be required and can be removed.

Implementing Defaulting Logic

In order to provide a place for defaulting logic the base class exposes the onApplyDefaults method. This method is called from the handleBeforeInsert base class method during a Trigger invocation. Placing logic here will ensure defaulting occurs consistently across the application. You may also choose to call it explicitly if needed.

The base class exposes the SObject list provided during the constructor call via the Records property to all methods. Although we are not strictly in a Trigger class here, we are still strongly encouraged to consider bulkificaiton, as per the domain design goals above.

public override void onApplyDefaults()
{
	// Apply defaults to Opportunities
	for(Opportunity opportunity : (List<Opportunity>) Records)
	{
		if(opportunity.DiscountType__c == null)
			opportunity.DiscountType__c = OpportunitySettings__c.getInstance().DiscountType__c;
	}
}

Implementing Validation Logic

Although you can override any of the trigger methods above to implement validation logic. It is platform best practice to do this only in the after phase of the Apex Trigger invocation. By overriding the onValidate method you can implement this logic in clearly defined place. Code in the base class methods handleAfterInsert and handleAfterUpdate ensure best practice is enforced by only calling this this method during this part of the Apex Trigger flow.

public override void onValidate()
{
	// Validate Opportunities
	for(Opportunity opp : (List<Opportunity>) Records)
	{
		if(opp.Type.startsWith('Existing') && opp.AccountId == null)
		{
			opp.AccountId.addError('You must provide an Account when ' + 
				'creating Opportunities for existing Customers.');	
		}			
	}
}


The above validate method will be called from the base class regardless of insert or create operations on the object, so it is good for implementing validations that apply regardless of which of these trigger operations is fired. However, if you require validation logic that is sensitive to data changing during update operations you can override the following variant.

public override void onValidate(Map<Id,SObject> existingRecords)
{
	// Validate changes to Opportunities
	for(Opportunity opp : (List<Opportunity>) Records)
	{
		Opportunity existingOpp = (Opportunity) existingRecords.get(opp.Id);
		if(opp.Type != existingOpp.Type)
		{
			opp.Type.addError('You cannot change the Opportunity type once it has been created');
		}
	}
}

Overriding the Apex Trigger Methods

Its not always the case that all domain logic you implement will fall into the above methods. Indeed its not a strict requirement to implement all defaulting or validation logic in them either. There may be cases where you want to extend or completely override the SObjectDomain trigger methods from those implemented in the base class.

To implement specific trigger related code, that invokes behaviours via other domain objects, the following is slightly contrived example of overriding the onAfterInsert method to update a field on related Accounts whenever new Opportunities are inserted.

public override void onAfterInsert()
{
	// Related Accounts
	List<Id> accountIds = new List<Id>();
	for(Opportunity opp : (List<Opportunity>) Records)
		if(opp.AccountId!=null)
			accountIds.add(opp.AccountId);
			
	// Update last Opportunity activity on related Accounts via the Accounts domain class
	SObjectUnitOfWork uow = new SObjectUnitOfWork(new Schema.SObjectType[] { Account.SObjectType }); 
	Accounts accounts = new Accounts([select ... from Account where id in :accountIds]);
	accounts.updateOpportunityActivity(uow);
	uow.commitWork();				
}


Note: The Accounts domain object is initialised using an inline SOQL, the next article in the series will introduce a new pattern that helps encapsulate query logic for better reuse and consistency around the resulting data, which is important to the domain class behaviours.

The above example also demonstrates the unit of work. In this case, its scope is that of the underlying Trigger event (note these methods are not directly called by the service layer). As such one is created and given to the Accounts domain method for it to register updates to the Account records. Delegation to the Accounts domain class is appropriate since this logic is more a behaviour of Account than Opportunity. Delegation of responsibilities is also illustrated in the Opportunties.applyDiscount method shown in the next section.

For reference here is the Accounts.updateOpportunityActivity method.

public class Accounts extends SObjectDomain
{
	public void updateOpportunityActivity(SObjectUnitOfWork uow)
	{
		for(Account account : (List<Account>) Records)
		{
			account.Description = 'Last Opportunity Raised ' + System.today();
			uow.registerDirty(account);
		}
	}
}

Implementing Other Domain Logic

You are not restricted to implementing only methods that are overridable from the base class. As shown in the following code, used by the service layer, is the ability to apply a discount to an Opportunity. Note that when required the code delegates to the OpportunityLineItems domain class to apply line level discounts.


public void applyDiscount(Decimal discountPercentage, SObjectUnitOfWork uow)
{
	// Calculate discount factor
	Decimal factor = Util.calculateDiscountFactor(discountPercentage);

	// Opportunity lines to apply discount to
	List<OpportunityLineItem> linesToApplyDiscount = new List<OpportunityLineItem>();
		
	// Apply discount 
	for(Opportunity opportunity : (List<Opportunity>) Records)
	{
		// Apply to the Opportunity Amount?
		if(opportunity.OpportunityLineItems.size()==0)
		{
			// Adjust the Amount on the Opportunity if no lines
			opportunity.Amount = opportunity.Amount * factor;
			uow.registerDirty(opportunity);
		}
		else
		{
			// Collect lines to apply discount to
			linesToApplyDiscount.addAll(opportunity.OpportunityLineItems);
		}
	}		
		
	// Apply discount to lines
	OpportunityLineItems lineItems = new OpportunityLineItems(linesToApplyDiscount);
	lineItems.applyDiscount(this, discountPercentage, uow);
}

Note: You can view the code for the OpportunityLineItems domain class here.

The SObjectUnitOfWork is taken as an argument so that the caller (in this case the OpportunitiesService.applyDiscount method) can pass it in for the domain class behaviours to register work against it. This also gets passed onto the OpportunityLineItems domain class applyDiscount method. For more information on the benifits of this pattern refer to the previous article.

Implementing Common Domain Behaviours

The above domain classes all extend the SObjectDomain class. To implement common behaviours across various objects in your application, you can choose to edit this class and add further behaviour or changes to it. There are some object oriented alternatives.

Interfaces

Consider for example that you have a number of objects in your application that are capable of generating invoices, however each has their own subtle differences in how this is achieved. You want to write a common invoicing service that creates invoices consistently regardless of which object type being processed. One option is to have the service expose an interface (or contract) that each of your domain classes implement.

public class InvoicingService
{
	public interface ISupportInvoicing 
	{
		void generate(InvoiceFactory invoiceFactory);
	}
}

public with sharing class Opportunities extends SObjectDomain
	implements InvoicingService.ISupportInvoicing
{
	public void generate(InvoicingService.InvoiceFactory invoiceFactory)
	{
		// Process Opportunities
		for(Opportunity opportunity : (List<Opportunity>) Records)
		{
			// Use InvoiceFactory to create invoices
			// invoiceFactory.createHeader, invoiceFactory.addInvoiceLine etc..
		}		
	}
}

Note: The invoicing service operation method to perform the invoicing and factory implementation are not important here.

Inheritance

Domain classes are not limited to inheriting functionality from a single base class. You can instead create one or more common base classes of your own (which then in turn extend SObectDomain). Depending on the object type you simply extend the base class that is most appropriate for the behaviour you want to apply.

Illustrated here is a slightly contrived Work Order object, with child objects that reflect the types of work items that can be performed. Each child object has a field that contains the cost for the work that was performed. The calculation of the work item cost is based on a common set of rules that must be calculated in Apex due to the complexity. The number of hours worked is also part of an Apex calculation, which is not common, and also requires Apex calculations.

AEP figure2.jpg

The requirement is to ensure that whenever any work item records are created or updated the cost field is updated accordingly.

Using inheritance the following object class diagram illustrates how domain classes are implemented for the three work item objects. This takes advantage of inheritance to share the common calculation logic and trigger handling.

AEP figure3.jpg

A new base class, Chargeable is created to encapsulate the cost calculation logic and override SObjectDomain methods to ensure the cost fields are updated during the insert and update trigger events. To fully implement this, the base class provides some methods of its own to override, to obtain the field to place the calculated cost in and method to receive the hours worked. The class is an abstract class, since on its own it is not useable.

public with sharing abstract class Chargeable extends SObjectDomain 
{
	public Chargeable(List<SObject> records)
	{
		super(records);
	}
	
	public abstract Schema.SObjectField getCostOfHoursWorkedField();
	 
	public abstract List<Integer> calculateHoursWorked();
	
	public override void onBeforeInsert()
	{
		updateCostOfHoursWorked();		
	}
	
	public override void onBeforeUpdate(Map<Id,SObject> existingRecords)
	{
		updateCostOfHoursWorked();
	}			
	
	private void updateCostOfHoursWorked()
	{
		List<Integer> hoursWorked = calculateHoursWorked();
		
		Schema.SobjectField costOfHoursWorkedField = getCostOfHoursWorkedField();
		
		Integer hoursWorkedRecordIdx = 0; 
		
		for(SObject record : Records)
		{
			record.put(costOfHoursWorkedField, hoursWorked[hoursWorkedRecordIdx++] * 100);	
		}
	}
}

The following shows how the above base class is used to implement a domain class for the Developer Work Item child object. As with the preceding Opportunities example a small Apex Trigger (not shown here) is also required to route Apex Trigger events to the SObjectDomain base methods and overridden by the Chargeable base class in this case.


public with sharing class DeveloperWorkItems extends Chargeable 
{
	public DeveloperWorkItems(List<DeveloperWorkItem__c> workItems)
	{
		super(workItems);
	}
	
	public override Schema.SObjectField getCostOfHoursWorkedField()
	{
            	// Field base class will populate once it has calculated the cost
		return DeveloperWorkItem__c.DeveloperCost__c;
	}
	 
	public override List<Integer> calculateHoursWorked()
	{
		// Calculate the hours worked for each record
		List<Integer> hoursWorked = new List<Integer>(); 
		for(DeveloperWorkItem__c workItem : (List<DeveloperWorkItem__c>) Records)
		{
			// This calculation is kept simplistic for illustration purposes
			hoursWorked.add((Integer)  
				(workItem.CodingHours__c + 
				 workItem.CodeReviewingHours__c + 
				 workItem.TechnicalDesignHours__c));
		}
		return hoursWorked;
	}	

	public class Constructor implements SObjectDomain.IConstructable
	{
		public SObjectDomain construct(List<SObject> sObjectList)
		{
			return new DeveloperWorkItems(sObjectList);
		}
	}	
}

Creating Logical / Conceptual Domain Classes

The examples given in this article, as well as the SObjectDomain base class, both focus on encapsulating data and behaviour from physical Custom Objects in your application. But what if you want to apply some of the domain layer design principles to data that is an aggregate from a number of objects and/or calculations in your application? This can be achieved by creating Apex classes that do not contain SObject data but custom data Apex classes (much like Java POJO’s). In order to explore this further it is important to introduce the Selector pattern, which will be covered in the next article.

Error Handling and Unit Testing Domain Classes

For those who favour TDD (Test Driven Development) you will hopefully find the benefits of factoring logic into smaller, more encapsulated chunks easier, since you can more easily construct the domain classes in your tests and invoke the methods directly. This does not mean you should not test your service layer as well, of course, but this does allow for a more incremental test and develop approach to be applied. Note that the SObjectDomain class does have some features not covered in this article that can help with this.

Summary

The domain layer offers a view of the code thats helps new and experienced developers find their way around a large application codebase more easily. The granularity this brings also aids in supporting unit test approaches such as Test Driven Development. By respecting SOC (Separation of Concerns) it helps ensure end users have a more consist experience. Regardless as to how they interact with your applications objects either directly or indirectly. To get the most out of this approach it is important to ensure you have a good understanding of Object Oriented Programming and how Apex supports it.

This article used a base class to help implement this pattern and at the same time enforce some best practices around bulkification and validation. However you choose to implement it, hopefully this article has given you some insight and inspiration as to how you can start to maximise the richness of Apex’s object oriented capabilities to achieve it. Next Article: Selector Layer In the final article of this series, the Selector (also otherwise known as the Data Mapper) pattern will be introduced. This will provide a means to add some reuse and consistency around how information is read from your objects and also the information passed around the system. Once again we will revisit the code shown in this article to illustrate this.

Links

Here are a few links to previous articles, other resources, the DF12 session and discussions of late that relate to this post. These will give you some foresight into upcoming topics I'll be discussing here. Enjoy!

About the Author

Andrew Fawcett is the CTO at FinancialForce.com and Force.com MVP. Follow Andrew on twitter, check out his personal blog and contributions on StackExchange.