Apex callout PATCH to Heroku Controller workaround

Setup a method to send Aync call to Heroku, add ‘?_HttpMethod=PATCH’ to the path to notify Heroku it’s a patch request

@future(callout=true)
	public static void callApiEndpointAsync(String apiEndpoint, String method, String aPayload){
		HttpRequest req = new HttpRequest();
		List<String> theArgs = new List<String>();
		HttpResponse res = new HttpResponse();
    try {
      if (apiEndpoint != null) {
        req.setTimeout(120000);
				if ('PATCH'.equals(method)){
					apiEndpoint += '?_HttpMethod=PATCH';
					req.setMethod('POST');
				} else {
					req.setMethod(method);
				}

        setAuthHeaderAsync(req);
        req.setEndpoint(herokuUrl + apiEndpoint);
				if (aPayload!=null)
					req.setBody(String.valueOf(aPayload));

				if (Test.isRunningTest() && (mock!=null)) {
					 mock.respond(req);
			 	} else {
					Integer retry = 0;

					while (retry < 2){
						Http http = new Http();
		        res =  http.send(req);
						if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) {
							retry +=2;
						} else if (res.getStatusCode()==503 || res.getStatusCode()==400){
							retry +=1;
							res = http.send(req);
						} else {
							retry +=1;
							theArgs.add('Api');
							theArgs.add(req.getEndpoint());
							theArgs.add(res.getBody());
						}
					}
					throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED', null, theArgs));
				}

      } else {
        System.debug('Service apiEndpoint and payload must not be null');
        throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED'));
      }
    } catch (Exception ex) {
			theArgs.add('Api');
			theArgs.add(req.getEndpoint());
			theArgs.add(res.getBody());
			throw new Rest_Exception(ResponseCodes_Mgr.getCode('HEROKU_REQUEST_CALLOUT_FAILED', ex, theArgs));
			
    }
}

Spring controller checks the RequestParam _HttpMethod to see if it’s a POST or PATCH request

@RequestMapping(value = "/customer", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
@ApiOperation(value = "Creates new payment customer")
@ApiResponses(value = {@ApiResponse(code = 200, message = "Creates new payment customer")})
public String createCustomer(@RequestBody PaymentCustomerWrapper paymentCustomerWrapper, @RequestParam(value="_HttpMethod", defaultValue="POST")  String httpMethod)
        throws IOException, URISyntaxException {
     if (httpMethod!=null && "PATCH".equals(httpMethod)){
         itsLogger.debug("update payment customer {}", paymentCustomerWrapper.toString());
         return paymentService.updatePaymentCustomer(paymentCustomerWrapper).toJson();
    } else {
         itsLogger.debug("create payment customer {}", paymentCustomerWrapper.toString());
         return paymentService.createChargeBeeCustomer(paymentCustomerWrapper).toJson();
    }
}

Heroku deploy local jar to Maven Dependencies

Sometime a jar is not available via Maven Central Repository and you need to load a jar from your local filesystem. Copy the jar file to a lib directory in your project.

Execute the following command via command line

mvn deploy:deploy-file -Durl=file:salesforce/lib/ 
-Dfile=salesforce/lib/emp-connector-0.0.1-SNAPSHOT-phat.jar 
-DgroupId=com.salesforce.conduit 
-Dpackaging=jar 
-Dversion=0.0.1-SNAPSHOT 
-DartifactId=emp-connector

Add the following to maven pom.xml

 <repositories>
    <repository>
        <id>project.local</id>
        <name>project</name>
        <url>file:${project.basedir}/lib</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
 </repositories>
 <dependencies>
    <dependency>
        <groupId>com.salesforce.conduit</groupId>
        <artifactId>emp-connector</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

Create a Salesforce EventBus

Create a new Planform Event Object and give it a name App Metadata (App_Metadata__e) which will also be the name of the topic. Create some fields you that you would like to listen to. In Apex add the following event

Build event payload

  List<App_Metadata.BPUpdateAccounts> updatedAccountList = new List<App_Metadata.BPUpdateAccounts>();
  App_Metadata.BPUpdateAccounts updatedAccount = new App_Metadata.BPUpdateAccounts();
  updatedAccount.setId(financialAccount.Id);
  updatedAccount.add(updatedAccount);
  new App_Rest_Events(new App_Rest_Events.Event(UserInfo.getUserId(), App_Rest_Events.EventType.UPDATE_ACCOUNT, 
  updatedAccountList)).publishToEventBus();

Publish Event to eventBus

  public void publishToEventBus(){
    Object eventPayload = buildEventPayload();
    EventBus.publish(new App_Metadata__e(Metadata_Json__c=JSON.serialize(eventPayload)));
  }

Now let’s create our event listen to subscript to the topic and consume the messages from the EventBus.

Add the emp-connector jar to project

<repositories>
        <repository>
            <id>emp-connector</id>
            <url>${project.basedir}/lib/emp-connector-0.0.1-SNAPSHOT-phat.jar</url>
        </repository>
    </repositories>

....

<dependency>
     <groupId>com.salesforce.conduit</groupId>
     <artifactId>emp-connector</artifactId>
     <version>0.0.1-SNAPSHOT</version>
</dependency>

Create TopicSubscription to your event table by name

package com.app.salesforce.eventbus;

import com.salesforce.emp.connector.BayeuxParameters;
import com.salesforce.emp.connector.EmpConnector;
import com.salesforce.emp.connector.TopicSubscription;

import java.net.URL;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import static com.salesforce.emp.connector.LoginHelper.login;

/**
 * Created by thysmichels on 5/15/17.
 */
public class SalesforceEventBus {

    private static final String SALESFORCE_USERNAME="salesforceUsername";
    private static final String SALESFORCE_PASSWORD="salesforcePassword";

    public static void main(String[] argv) throws Exception {

        long replayFrom = EmpConnector.REPLAY_FROM_EARLIEST;

        BayeuxParameters params;
        BayeuxParameters custom = getBayeuxParamWithSpecifiedAPIVersion("39.0");
        try {
            params = login(new URL("https://test.salesforce.com"),SALESFORCE_USERNAME, SALESFORCE_PASSWORD, custom);

        } catch (Exception e) {
            e.printStackTrace(System.err);
            System.exit(1);
            throw e;
        }

        Consumer<Map<String, Object>> consumer = event -> System.out.println(String.format("Received:\n%s", event));
        EmpConnector connector = new EmpConnector(params);

        connector.start().get(5, TimeUnit.SECONDS);

        TopicSubscription subscription = connector.subscribe("/event/App_Metadata__e", replayFrom, consumer).get(5, TimeUnit.SECONDS);

        System.out.println(String.format("Subscribed: %s", subscription));
    }


    private static BayeuxParameters getBayeuxParamWithSpecifiedAPIVersion(String apiVersion) {
        BayeuxParameters params = new BayeuxParameters() {

            @Override
            public String version() {
                return apiVersion;
            }

            @Override
            public String bearerToken() {
                return null;
            }

        };
        return  params;
    }
}

Topic receives response from subscribed channel

Subscribed: Subscription [/event/App_Metadata__e:-2]
Received:
{schema=D6-eSgLDrahnNjuNI52XAg, payload={CreatedById=00521000000UcF0, CreatedDate=2017-05-15T18:35:10Z, 
Metadata_Json__c="{\"accounts\":[{\"id\":\"28706236\",\"providerAccountId\":14736,\"sfId\":\"a0721000001A6fOAAS\"},
{\"id\":\"YL1111\",\"providerAccountId\":1466,\"sfId\":\"a0721000001A6fPAAS\"}],\"event\":\"UPDATE_GOAL\",\"linkAccounts\":null,
\"financialAccountMap\":null,\"goals\":[{\"id\":\"a0821000000OVPUAA4\",\"linkedAccounts\":[\"a0721000001ADqkAAG\"],
\"status\":\"In Progress\"},{\"id\":\"a0821000000OVPZAA4\",\"linkedAccounts\":null,\"status\":\"In Progress\"},{\"id\":\"a0821000000OVPeAAO\",\"linkedAccounts\":[\"a0721000001ADqaAAG\"],\"status\":\"In Progress\"}],
\"linkUserContext\":{\"accountList\":null,\"additionalProperties\":null,\"deleteAccountList\":null,\"email\":\"blah@email.com\",
\"password\":\"p@assw0rd\",
\"providerAccountIds\":null,\"sfClientId\":\"0033600000NO1QjAAL\",\"sfHouseholdId\":\"0013600000VNXflAAH\",\"sfUserId\":\"00521000000UcF0AAK\",
\"type\":\"YL\",\"username\":\"YL00F0A\"},\"priority\":5,\"providerAccounts\":null,
\"updatedAccounts\":null,\"updatedGoals\":[{\"id\":\"a0821000000OVPZAA4\",\"linkedAccounts\":null}],\"mapKeys\":{\"event\":\"event\",\"priority\":\"priority\",\"linkUserContext\":\"linkUserContext\",\"goals\":\"goals\",
\"accounts\":\"accounts\",\"providerAccounts\":\"providerAccounts\",\"updatedAccounts\":\"updatedAccounts\",\"updatedGoals\":
\"updatedGoals\",\"goalFinancialAccountMap\":\"goalFinancialAccountMap\",\"fastlinkAccounts\":\"fastlinkAccounts\"},
\"mapKeysFlag\":true,\"serializeNulls\":false}"}, event={replayId=1}}

Run Spring Boot Scheduler as Heroku Worker

1. Setup Application
2. Setup Scheduler
3. Add Spring Boot dependency
4. Add Procfile
5. Scale Heroku worker

1. Setup Application

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

/**
 * Created by tmichels on 9/20/15.
 */

@SpringBootApplication
@EnableScheduling
public class Application {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class);
    }
}

2. Setup Scheduler

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Set;

/**
 * Created by tmichels on 9/18/15.
 */

@Component
public class Scheduler {

    private static final Logger logger = LoggerFactory.getLogger(Scheduler.class);

    @Scheduled(cron = "0 0 13 * * *")
    public void buyHighPercentageReturnNotes(){
       System.out.println("Scheduler is running");
    }
}

3. Add Spring Boot dependency

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.6.RELEASE</version>
    </parent>
  <dependencies>
      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
      </dependency>
  </dependencies>

4. Add Procfile

worker: java $JAVA_OPTS -jar target/*.jar

5. Scale Heroku worker

heroku ps:scale worker=1

Heroku OAuth 2.0 Scribe Spring MVC Example

ApplicationContext.xml defines herokuServiceConfig and herokuServiceProvider Bean

  <bean id="herokuServiceConfig" class="com.example.oauth.OAuthServiceConfig">
		<constructor-arg value="xxx" />
		<constructor-arg value="xxx"/>
		<constructor-arg value="https://www.example.com/oauth/heroku"/>
		<constructor-arg value="com.example.oauth.HerokuOauthApi"/>
	</bean>
	
	<bean id="herokuServiceProvider" class="com.example.oauth.OAuthServiceProvider">
		<constructor-arg name="config" ref="herokuServiceConfig" />
	</bean>

Spring MVC Heroku OAuth Controller

package com.example.oauth.controller;

import static org.springframework.web.context.request.RequestAttributes.SCOPE_SESSION;

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;

import com.example.oauth.OAuthServiceProvider;

/**
 * @author tmichels
 */

@Controller
@RequestMapping("/oauth/heroku")
public class HerokuController {
	
	@Autowired
	@Qualifier("herokuServiceProvider")
	private OAuthServiceProvider herokuServiceProvider;
	
	private static final Token EMPTY_TOKEN = null;
	
	@RequestMapping(value="/login-heroku", method=RequestMethod.GET)
	 public String loginToHeroku(Map<String, Object> map, WebRequest request) {
			OAuthService service = herokuServiceProvider.getService();
			String authUrl = service.getAuthorizationUrl(EMPTY_TOKEN);
			System.out.println("RequestToken: " + authUrl );
			return "redirect:" + authUrl;
    }
	
	@RequestMapping(value={""}, method = RequestMethod.GET)
	public String callback(@RequestParam(value="oauth_token", required=false) String oauthToken,
			@RequestParam(value="code", required=false) String oauthVerifier, WebRequest request, Map<String, Object> map) {

		OAuthService service = herokuServiceProvider.getService();

		// getting access token
		Verifier verifier = new Verifier(oauthVerifier);
		Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier);

		// store access token as a session attribute
		request.setAttribute("oauthAccessToken", accessToken, SCOPE_SESSION);

		ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
		HttpSession session = attr.getRequest().getSession(false); //create a new session
		session.setAttribute("accessToken",accessToken);
		
		return "settings";
	}
		
}

HerokuOauthApi extends DefaultApi20

package com.example.oauth;

import org.scribe.builder.api.DefaultApi20;
import org.scribe.extractors.AccessTokenExtractor;
import org.scribe.extractors.JsonTokenExtractor;
import org.scribe.model.OAuthConfig;
import org.scribe.model.Verb;

public class HerokuOauthApi extends DefaultApi20{

	private static final String ACCESSTOKEN = "https://id.heroku.com/oauth/token";

	@Override
	public String getAccessTokenEndpoint() {
		return ACCESSTOKEN;
	}

	@Override
	public String getAuthorizationUrl(OAuthConfig config) {
		return String.format("https://id.heroku.com/oauth/authorize?client_id=%s&response_type=code&scope=global", config.getApiKey());
	}
	
	 @Override
	 public Verb getAccessTokenVerb(){
	       return Verb.POST;
	 }
	 
	 @Override
	 public AccessTokenExtractor getAccessTokenExtractor() {
	    return new JsonTokenExtractor();
	 }
}

Setup Custom Domain on Heroku from GoDaddy

Add new custom domain to Heroku

heroku domains:add www.example.com
heroku domains:add example.com

Add both with www. and no www. domain names.

It will look like the following
Screen Shot 2014-04-23 at 5.54.34 PM

Login to Godaddy and add/edit host:www to your heroku app, it will looks like follows:
Screen Shot 2014-04-23 at 5.50.46 PM

Add a forwarding rule to that http://example.com will be directed to http://www.example.com
Screen Shot 2014-04-23 at 5.50.21 PM

New-Relic Add-on for Java Application on Heroku Setup

Add New Relic Agent Dependency

 <dependency>
  	<groupId>com.newrelic.agent.java</groupId>
  	<artifactId>newrelic-agent</artifactId>
  	<version>3.5.1</version>
  	<scope>provided</scope>
</dependency>

pom.xml build tag

  <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
                <version>2.3.2</version>
            </plugin>
            <plugin>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.2</version>
            </plugin>
            
            <plugin>
				<groupId>com.mysema.maven</groupId>
				<artifactId>apt-maven-plugin</artifactId>
				<version>1.0.8</version>
				<dependencies>
					<dependency>
						<groupId>com.mysema.querydsl</groupId>
						<artifactId>querydsl-apt</artifactId>
						<version>2.9.0</version>
					</dependency>
				</dependencies>
				<executions>
					<execution>
						<phase>generate-sources</phase>
						<goals>
							<goal>process</goal>
						</goals>
						<configuration>
							<outputDirectory>target/generated-sources</outputDirectory>
							<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
						</configuration>
					</execution>
				</executions>
			</plugin>
            
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.6</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                           <goal>copy</goal>
                        </goals>
                         
                        <configuration>
                            <artifactItems>
                                <artifactItem>
                                    <groupId>com.github.jsimone</groupId>
                                    <artifactId>webapp-runner</artifactId>
                                    <version>7.0.22.3</version>
                                    <destFileName>webapp-runner.jar</destFileName>
                                </artifactItem>
                            </artifactItems>
                        </configuration>
                    </execution>
                   <execution>
         				 <id>copy-new-relic</id>
          					<phase>package</phase>
         				<goals>
          					 <goal>copy-dependencies</goal>
         				 </goals>
         				 <configuration>
           					 <includeGroupIds>com.newrelic.agent.java</includeGroupIds>
           					 <includeArtifactIds>newrelic-agent</includeArtifactIds>
           					 <stripVersion>true</stripVersion>
          					</configuration>
        			</execution>
                </executions>
            </plugin>
        </plugins>
        <pluginManagement>
	<plugins>
    <!-- Ignore/Execute plugin execution -->
    <plugin>
        <groupId>org.eclipse.m2e</groupId>
        <artifactId>lifecycle-mapping</artifactId>
        <version>1.0.0</version>
        <configuration>
            <lifecycleMappingMetadata>
                <pluginExecutions>
                    <!-- copy-dependency plugin -->
                    <pluginExecution>
                        <pluginExecutionFilter>
                            <groupId>org.apache.maven.plugins</groupId>
                            <artifactId>maven-dependency-plugin</artifactId>
                            <versionRange>[1.0.0,)</versionRange>
                            <goals>
                                <goal>copy-dependencies</goal>
                            </goals>
                        </pluginExecutionFilter>
                        <action>
                            <ignore />
                        </action>
                    </pluginExecution>
                </pluginExecutions>
            </lifecycleMappingMetadata>
        </configuration>
    </plugin>
   </plugins></pluginManagement>
        
    </build>
    <name>CloudSole Developer</name>
    <description>CloudSole Developer</description>
    <organization>
    	<name>Thys Michels</name>
    	<url>http://www.thysmichels.com</url>
    </organization>

Create newrelic.yml and save to project route (same level as pom.xml)

.....
license_key: 'OURLICENCEKEYHERE'
....
app_name: CloudSole
....

Add the following flag to your JAVA_OPTS config var on Heroku:

heroku config:set JAVA_OPTS='-Xmx384m -Xss512k -XX:+UseCompressedOops -javaagent:target/dependency/newrelic-agent.jar' NEW_RELIC_APP_NAME="CloudSole"

Heroku Elastic Search Example

Elastic search configuration

package com.example.elasticsearch;

import io.searchbox.client.JestClient;
import io.searchbox.client.JestClientFactory;
import io.searchbox.client.config.ClientConfig;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JestConfiguration {

	@Bean
	public JestClient jestClient(){
		// Configuration
		ClientConfig clientConfig = new ClientConfig.Builder(System.getenv("SEARCHBOX_URL"))
		.multiThreaded(true).build();

		// Construct a new Jest client according to configuration via factory
		JestClientFactory factory = new JestClientFactory();
		factory.setClientConfig(clientConfig);
		JestClient client = factory.getObject();
		return client;
	}
}

Elastic search service

package com.example.elasticsearch;

import java.util.List;

import io.searchbox.client.JestClient;
import io.searchbox.client.JestResult;
import io.searchbox.core.Bulk;
import io.searchbox.core.Index;
import io.searchbox.core.Search;
import io.searchbox.indices.CreateIndex;
import io.searchbox.indices.IndicesExists;

import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Service;

import com.example.jpa.JpaCustomerService;
import com.example.model.Customer;
import com.example.service.CustomerService;

@Service
@Import(JpaCustomerService.class)
public class JestService {

	@Autowired @Qualifier("jpaRepository") CustomerService jpaCustomerService;
	
	@Autowired JestClient jestClient;
	
    @Autowired
	public void createCustomerIndex(){
		try {
			IndicesExists indicesExists = new IndicesExists.Builder().build();
	        JestResult result = jestClient.execute(indicesExists);

            if (!result.isSucceeded()) {
                // Create customer index
                CreateIndex createIndex = new CreateIndex.Builder("customers").build();
                jestClient.execute(createIndex);
            }
      
            Bulk bulk = new Bulk.Builder()
		 	.addAction(new Index.Builder(jpaCustomerService.getAllCustomers()).index("customer").type("customer").build())
		    .build();

            result = jestClient.execute(bulk);

            System.out.println(result.getJsonString());
	
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public List<Customer> searchCustomers(String customerQuery){
		try {
			SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
	        searchSourceBuilder.query(QueryBuilders.queryString(customerQuery));
	        
	        Search search = new Search.Builder(searchSourceBuilder.toString())
	        .addIndex("customer")
	        .addType("customer")
	        .build();
	
	        JestResult result = jestClient.execute(search);
			return result.getSourceAsObjectList(Customer.class);
		        
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		 return null;
	}
}

Heroku MemCached Junit Test

Heroku Memcached Cloud Junit Test

package com.example.cache;

import static org.junit.Assert.*;

import java.io.IOException;
import java.math.BigInteger;

import net.spy.memcached.AddrUtil;
import net.spy.memcached.ConnectionFactoryBuilder;
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.auth.AuthDescriptor;
import net.spy.memcached.auth.PlainCallbackHandler;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.example.model.Customer;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class CacheCustomerServiceTest {
	
	private static final String USERNAME="######";
	private static final String PASSWORD="######";
	private static final String HOST="######";
	
	@Configuration
	static class ContextConfiguration{
		
		@Bean
		public MemcachedClient memcachedConfig() {
		try{
			 AuthDescriptor ad = new AuthDescriptor(new String[] { "PLAIN" },
				        new PlainCallbackHandler(USERNAME, PASSWORD));

			MemcachedClient mc = new MemcachedClient(
					new ConnectionFactoryBuilder()
				                  .setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
				                  .setAuthDescriptor(ad).build(),
		    AddrUtil.getAddresses(HOST));
			return mc;
		} catch (IOException ex) {}
			return null;
		}
	}
	
	@Autowired MemcachedClient memcachedConfig;
	
	@Test
	public void testMemCachedSet() {
		memcachedConfig.set("customerKey", 3600, new Customer(new BigInteger("1"), "John", "Doe"));
		Object value = memcachedConfig.get("customerKey");
		System.out.println(value);
	}
}

Setup Memcached on Heroku

Add memcachedcloud to your project:
heroku addons:add memcachedcloud

1. Setup Memcached Configuration
2. Setup Memcached Service

1. Setup Memcached Configuration

package com.example.cache;

import java.io.IOException;

import net.spy.memcached.AddrUtil;
import net.spy.memcached.ConnectionFactoryBuilder;
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.auth.AuthDescriptor;
import net.spy.memcached.auth.PlainCallbackHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CacheConfiguration {
	
	@Bean
	public MemcachedClient memcachedCloudConfiguration() {
		try {
		    AuthDescriptor ad = new AuthDescriptor(new String[] { "PLAIN" },
		        new PlainCallbackHandler(System.getenv("MEMCACHEDCLOUD_USERNAME"), System.getenv("MEMCACHEDCLOUD_PASSWORD")));

		    MemcachedClient mc = new MemcachedClient(
		              new ConnectionFactoryBuilder()
		                  .setProtocol(ConnectionFactoryBuilder.Protocol.BINARY)
		                  .setAuthDescriptor(ad).build(),
		          AddrUtil.getAddresses(System.getenv("MEMCACHEDCLOUD_SERVERS")));
		    return mc;
		} catch (IOException ex) {
		    // the Memcached client could not be initialized.
		}
		return null;
	}
}

2. Setup Memcached Service

package com.example.cache;

import java.math.BigInteger;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import net.spy.memcached.MemcachedClient;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Service;

import com.example.model.Customer;
import com.example.service.CustomerService;

@Service
@Import({CacheConfiguration.class})
public class CacheCustomerService implements CustomerService{

	@Autowired MemcachedClient memCachedClient;
	
	@Override
	public Customer getCustomerById(BigInteger id) {
		return (Customer) memCachedClient.get(String.valueOf(id));
	}

	@Override
	public void createCustomer(String fn, String ln) {
		memCachedClient.set("customerKey", 3600, new Customer(fn, ln));
	}

	@Override
	public void deleteCustomer(BigInteger id) {
		memCachedClient.delete(String.valueOf(id));
	}

	@Override
	public void createCustomer(Customer newCustomer) {
		memCachedClient.set("customerKey", 3600, newCustomer);
	}	
}