Contents

Introduction

In the previous article, SOC (Separation of Concerns) was discussed as a means to focus software architects into thinking about layering application logic. This next article in the series focuses on arguably the most important layer of them all, the Service layer. This is because, it is the entry point to the most important development investment and asset of all. Your applications business logic layer, it’s very heart!

Service Layer, “Defines an application's boundary with a layer of services that establishes a set of available operations and coordinates the application's response in each operation.” Martin Fowler / Randy Stafford, EAA Patterns

A Service Layer will help you form a clear and strict encapsulation of code implementing business tasks, calculations and processes. For now this article focuses on defining and utilising the Service Layer as a key entry point to other layers and consumers (such as an API) of your application. While this article offers some insight (such as the Unit of Work pattern) as to how to help implement logic in this layer, this aspect is mostly left outside the scope of this article. Until the next article in this series focusing on the Domain Layer.

It is key to ensure a Service Layer is ready for use in different contexts. Such as mobile applications, UI forms, rich web ui’s, numerous API’s etc. It must remain pure and abstract to ensure it endures the changing times and demands ahead of it. The following sections will discuss, illustrate and define the rules for creating a Service layer implementation in Apex. While also emboding Force.com best practices to ensure it is a good fit governor wise to related Force.com technologies and contexts.

Who uses the Service layer?

The term ‘clients’ can be used as a general term to describe those who consume the service layer, those who effectively invoke your service layer code. In this context they are not then, human clients as such, servicing them is the UI presentation layer job!

A good example of a client would be code within a Visualforce Controller. However an often overlooked consideration, is that there are many other equally valid clients (callers) of your service layer code. If you start to think about all the ways in which application logic on the Force.com platform can get invoked you can build a potential list candidates as follows.

Service layer.jpg

Note: You may have noticed Apex Triggers is missing. This is because logic here belongs to your applications Domain Layer, which is closely aligned with the objects in your application. This logic is called both directly and indirectly within the service layer and of course the platform UI and API’s. It will be discussed in more detail in a later article.

As you can imagine, it is all too easy to start to ‘leak’ service layer logic into the Apex code created for other layers and purposes in your application. Such leaks erode the value of implementing a Service Layer. As they give rise to inconsistencies filtering up to your end users experience. As users interact with your application through your features exposed by one or more of the above Force.com technologies. In the next section we will discuss the design and responsibilities of the service layer and thus the expectations code written in the above areas should have when using it.

Platform Innovation and Adaptability

You may have recognised the list of technologies above is presented in order of them being introduced across the last 3 years by Salesforce. Imagine how much easier it would be to adopt and adapt your application to these features and upcoming ones, if you didn’t have to worry about refactoring your code from one of the previous areas first. Or worse duplicating the code, because you fear refactoring will break existing functionality.

Design Considerations

  • Naming Conventions. As discussed above the Service layer must be abstract enough to be meaningful to a number of clients. A key aspect of this often falls to the verbs and nouns you use in class names, method and parameter names. Ensuring they are expressed in general terms of the application/task itself rather than relating to any one specific client caller. For example:
 InvoiceService.calculateTax(...)

vs

InvoiceService.handleTaxCodeTabout(...)
  • Platform / Caller Sympathy. Design in sympathy with the hosting platform, as its a sure bet the demands the most immediate clients put on your service will also have the same considerations to deal with. One of the main concerns of all code on Force.com is bulkificaiton, as such it is best to consider services that can be called with lists vs single parameter sets. For example:
InvoiceService.calculateTax(List<TaxCalulation> taxCalulations)

vs

 InvoiceService.calculateTax(Invoice invoice, TaxInfo taxCodeInfo)
  • SOC Considerations. Service layer code pulls together task or process logic typically utilising multiple object data in your application, think of it as an orchestrator. Typically code associated with the behaviour of your objects in our application will be associated with those objects in separate classes and invoked via Apex Triggers. This type of code is known as your Domain Layer code.
  • Marshalling. Avoid prescribing how aspects of interacting with the service layer will be handled. As certain aspects are better left to the callers of your service. For example, semantics like error handling and messaging. Callers often have their own means to interpret and handle these. e.g. VF uses <apex:pagemessages> and Schedule jobs, will likely use emails, chatter posts or logs to communicate errors. So in this case it is typically best leverage the default error handling semantics of Apex by throwing exceptions..
  • Compound Services. While clients can execute multiple service calls together this can often be inefficient and lead to them having to be concerned with managing database transactional issues (see following design consideration). Thus it is better to consider creating ‘compound’ services that effectively group internally multiple service calls together in one service call. This is also important to ensure the service layer is as optimised as possible in respect to SOQL and DML usage. This does not mean more granular services cannot be exposed, it just means that the option to use a more specific single service should be given to the callers as well if needed. This is often only determined as an when use cases arise, through careful planning is always the best time to determine this.
  • Transaction Management and Statelessness. Clients of the service layer often have different concepts as to the longevity of the actual process or operation being undertaken. If it spans a single request to the server, chunked into separate scopes (aka batch apex) or if it maintains its own state across several requests (vf or rich client), until such time the database needs updating. Given these contexts and keeping in mind the creation of compound services where needed. It is often best to encapsulate database operations and service state within the scope of the service layer operation. This helps calling contexts employ their own solutions assuming a standard behavior throughout your service layer. Also, consider the effect a failure may have on any objects passed into a transaction. Will they be left dirty if the transaction fails? For example, if you commit object A and object B in a transaction and object B fails to commit, the database rolls back but object A is left with an Id populated which may cause trouble for the client. One technique is to clone any objects passed into the service at the start and let your service act on the clones leaving the original objects clean in case of failure.
  • Configuration. You might have common configuration or behavioural overrides in a service layer. Such as providing control to allow the client to instruct the server layer not to commit changes or send emails. This for example, might be useful in cases where the client is implementing preview or what-if type functionality. Be sure to consider how you implement this consistently, perhaps as a method overload that takes a shared Options parameter, similar to the DML methods in Apex.

IMPORTANT NOTE: In Apex database transactions are auto committed if the request completes without error and rolled back in the event of an unhandled exception. However allowing a request to complete with errors being thrown is not sometimes the user experience desired, since the platform handling of these exceptions is often not all that accessible (Batch Apex jobs) or esthetically pleasing (white page black text) to end users. For this reason developers often catch exceptions and route them accordingly. A potential side effect with this approach is that the platform sees this is a valid completion of the request. And automatically commits records that have been inserted or updated leading upto the actual error that occurred. As such aggregating of database operations and transaction rollbacks within the service layer is a consideration.

Creating Services

The following illustrates in Apex the design considerations described above in more detail. If you're going with full encapsulation of database and state management as outlined above. One implementation approach is really just an appropriately named class with static methods representing the operations of the service.

The methods represent the service operations, which access information they need through the environment and parameters passed. While the logic in the method is updating the database and/or returning a set of information in the methods return type. Utilising custom Apex exceptions to indicate failure. The following example shows a service to apply a given discount to a set of Opportunities (and lines items if present).

public class OpportunityService
{
	public static void applyDiscounts(Set<ID> opportunityIds, Decimal discountPercentage)
	{
		if(opportunityIds==null || opportunityIds.size()==0)
			throw new OpportunityServiceException('Opportunities not specified.');
		if(discountPercentage<0 || discountPercentage>100)
			throw new OpportunityServiceException('Invalid discount to apply.');

		// Query Opportunities and Lines
		List<Opportunity> opportunities = // ...

		// Update Opportunities and Lines (if present)
		List<Opportunity> oppsToUpdate = ....
		List<OpportunityLineItem> oppLinesToUpdate = ....
		for(Opportunity : opportunities)
		{
			// Apply to Opportunity Amount or Product Lines?
			// ...
		}

		// 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;
		}
	}

	public class OpportunityServiceException extends Exception
	{
		// Add members and methods here to communicate data
		// relating to exceptions
	}
}

If we apply the configuration design consideration described earlier. You might want to add an overloaded version of the above service with an Options parameter that allows the caller to instruct the service to skip committing the work. Then by returning the records this would allow the client to implement a ‘preview’ of the discounts that would be applied.

public static List<Opportunity> applyDiscounts(
     Set<ID> opportunityIds, Decimal discountPercentage, Options config)

Also notice that the method signature in the full example above takes a list of Id's, this is as per the design considerations. However only a single parameter for the discount was used. The assumption here is that the same discount is applied to all opportunities. However if allowing different discounts per opportunity is required a parameter class can be used.

public class OpportunityService
{
	public class ApplyDiscountInfo
	{
		public Id OpportunityId;
		public Decimal DiscountPercentage;
	}

	public static void applyDiscounts(List<ApplyDiscountInfo> discInfos)

Unit Of Work Pattern

This section introduces a design pattern that can help streamline transaction management and help reduce the coding complexities of adhering to DML bulkificaiton. It is by no means a requirement for implementing a service layer, but can help.

In order to truly encapsulate and scope the database operations within the service layer method a Savepoint was used in the above example. Recall as per the design considerations, this was used to avoid the caller catching exceptions (perhaps resulting from updating the opportunities, second DML statement). That would then result in the Apex runtime only committing updates to the opportunities lines (first DML statement).

In addition while the example given above did adhere to bulkifcaiton of DML operations, in larger more complex code bases and deeper code execution paths, this is not always that straightforward. The following example is shows how an implementation of the Unit Of Work pattern simplifies things for the developer in both regards.

public static void applyDiscounts(Set<ID> opportunityIds, Decimal discountPercentage)
{
	// Query Opportunities and Lines
	List<Opportunity> opportunities = // ...

	// Update Opportunities and Lines (if present)
	SObjectUnitOfWork uow = new SObjectUnitOfWork(SERVICE_SOBJECTS);
	for(Opportunity opportunity : opportunities)
	{
		// Apply to Opportunity Amount or Product Lines?
		// ...
		uow.registerUpdate(opportunity);
		// or
		uow.registerUpdate(opportunityLine);
	}

	// Update the database
	uow.commitWork();
}

The SObjectUnitOfWork class aggregates DML operations and automatically wraps them in a Savepoint as per the example above when the commitWork method is called. A full discussion on the SObjectUnitOfWork class is outside the scope of this article. For now you can review the comments and unit tests for more information of the class here. There is also further discussion in this video.

NOTE: In a more complex code, with multiple depths and classes, you can choose to pass the SObjectUnitOfWork (or use a static) so that called code can continue to register its own database updates. Knowing that the owner of the Unit Of Work, in this case the service layer will perform a single commit or rollback phase on its behalf.

Using Services

Lets see how the above service layer can be utilised from a number of clients, Visualforce, Batch Apex and JavaScript Remoting are all shown below. The following example deals with a single Opportunity selected via a StandardController.

public PageReference applyDiscount()
{
	try
	{
		// Apply discount entered to the current Opportunity
		OpportunitiesService.applyDiscounts(
			new Set<ID> { standardController.getId() }, DiscountPercentage);
	}
	catch (Exception e)
	{
		ApexPages.addMessages(e);
	}			
	return ApexPages.hasMessages() ? null : standardController.view();
}

The following example deals with multiple Opportunities via a StandardSetController.

public PageReference applyDiscounts()
{
	try
	{
		// Apply discount entered to the selected Opportunities
		OpportunitiesService.applyDiscounts(
		   new Map<Id,SObject>(standardSetController.getSelected()).keyValues(), 
		   DiscountPercentage);
	}
	catch (Exception e)
	{
		ApexPages.addMessages(e);
	}			
	return ApexPages.hasMessages() ? null : standardController.view();				
}

The following example deals with processing chunks of records via the Batch Apex execute method. Note how the exception handling is different to that of the Visualforce controller example shown above.

public with sharing class DiscountOpportunities 
	implements Database.Batchable<sObject>
{
	public Decimal DiscountPercentage {get;set;}

	public Database.QueryLocator start(Database.BatchableContext BC) { ... }

	public void execute(Database.BatchableContext BC, List<sObject> scope)
	{
		try
		{
			OpportunitiesService.applyDiscounts(
		 		new Map<Id,SObject>(scope).keyValues(),DiscountPercentage);
		}
		catch (Exception e)
		{
			// Email error, log error, chatter error etc..
		}
	}

	public void finish(Database.BatchableContext BC) { ... }
}

BATCH APEX NOTE: Above the SObject data passed in as the scope of the execute, is ignored and only the ID’s are used. This actually relates to another general best practice with Batch Apex. Because it runs in the background over potentially several minutes or hours, the data in the SObject records passed in can be quite stale. Due to the platform caching the original SOQL resultset that started the job. Thus typically its best to requery.

The following example wraps the service method and exposes it via JavaScript Remoting. Note that exceptions are not caught here, since JavaScript Remoting has inbuilt marshalling of exceptions when thrown. We want to leverage this, passing them onto JavaScript client code to catch using its inbuilt facilities.

public class OpportunityController
{
	@RemoteAction
	public static String applyDiscount(Id opportunityId, Decimal discountPercent)
	{
		// Call service
		OpportunitiesService.applyDiscounts(new Set<ID> { opportunityId }, discountPercent);
	}
}

Exposing Services to External Clients as an API

There are many ways to expose Apex code to external parties through an API into your application. In the age of cloud applications, it is a must if you are to develop a strong ecosystem of innovation and partner integrations around your products.

So if you consider your service layer as fully tested, robust and open to any client (and why would it not be, since your’re using it as well right?). Then the simplest way to expose it to Apex developers is to modify the class and method modifiers from ‘public’ to ‘global’.

global class OpportunityService
{
	global class ApplyDiscountInfo
	{
		global Id OpportunityId;
		global Decimal DiscountPercentage;
	}

	global static void applyDiscounts(List<ApplyDiscountInfo> discInfos)

NOTE: There are implications with using global in terms of changing such method signatures and class definitions once your package is released. However you can add or extend parts later, and even change the code behind. This restriction is in place and managed by the platform to allow you to help version your API and avoid breaking existing integrations during upgrades. By using the @depricated annotation new customers cannot see older parts of your API, yet existing customers continue to be supported. If you would rather keep your service layer away from such considerations then you might want to simply create specific classes for an API, that delegates to your service layer.

It also worth considering exposing your API for off platform callers. One way to do this is via the REST protocol. Below is a custom Apex REST API implementation. Here the ID of the opportunity is taken from the URI and the discount percentage from a URI parameter. Also as with the JavaScript Remoting example above, exception handling is left to the caller, the platform will automatically marshall exceptions into the appropriate JSON/XML response.

@RestResource(urlMapping='/opportunity/*/applydiscount')
global with sharing class OpportunityResource 
{
	@HttpGet
	global static void applyDiscount()
	{
		// Parse context
		RestRequest req = RestContext.request;
		String[] uriParts = req.requestURI.split('/');
		Id opportunityId = uriParts[2];
		Decimal discountPercentage = Decimal.valueOf(req.parameters.get('discountPercentage'));

		// Call service
		OpportunitiesService.applyDiscounts(
			new Set<ID> { opportunityId }, DiscountPercentage);
	}
}

Other Benefits and Considerations

Outside the scope of this particular article is the topic of implementing services, for mock testing and parallel development. Services can utilise the factory pattern along with Apex interfaces to dynamically resolve the implementation, as opposed to coding it directly in the methods themselves. This can be useful for giving more flexibility in engineering the scope of tests around services. As well providing the opportunity to define up front the service layer and then have developers or teams working together in parallel. Those needing to call the services can utilise dummy implementations returning static data, meanwhile those implementing the services can work on the code without impacting their callers. This style of development is also often referred to as Design by Contract (Dbc).

Summary

Investing in a service layer for your application can not only give you engineering benefits, for greater reuse and adaptability. But also provide a cleaner and more cost effective way of implementing an API for your application, a must in todays cloud integrated world. Ultimately by closely observing the encapsulation and design considerations described above you start to form a durable core for your application. One that will endure and remain a solid investment throughout the ever changing and innovative times ahead!

The next article in this series will deal with a layer in your application known as the Domain Layer. This layer concerns itself with providing a standard means of encapsulating and managing the Apex code implementing the validations, defaulting and other logic relating to the objects in your application. Both physical (custom object) and logical (conceptual). It is mostly invoked through CRUD operations against your objects invoked via Apex Triggers. It and the Services in your application also have specific responsibilities and interactions between each other, we will be revisiting the samples shown in this article to illustrate this.

Links

Here are a few links to 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.