Cloud computing isn’t just about application deployment. There are also a growing number of cloud-based web services that you can use to develop your application. One of the most well known is Amazon’s Simple Storage Service. But there are many others including web services for messaging, relational and NoSQL databases, email and telephony. Using these services allows you to build highly scalable applications without the pain and cost of having to develop and operate your own infrastructure.
In this presentation, you will learn about the benefits and drawbacks of these Web services; their typical use cases and how to use them. We will describe a location aware, telephony application that is built using cloud services. You will learn about strategies for building resilient, fault tolerant applications that consume cloud services.
Developing applications with Cloud Services (Devnexus 2013)
1. DEVELOPING WITH CLOUD
SERVICES
Chris Richardson
Author of POJOs in Action
Founder of the original CloudFoundry.com
@crichardson
chris.richardson@springsource.com
http://plainoldobjects.com
2. Presentation goal
How to build robust,
scalable applications with
Cloud Services
@crichardson
13. Solved by VoteMeetEat.com
•What restaurants are nearby?
•Which friends are close by?
•Where do your friends prefer to eat?
To sign up text
"register" to 510-
XXX-YYYY
@crichardson
20. High-level architecture
DIY = DIFFICULT
Telephony Friend Geo
Integration Database
Mobile VoteMeet
Phone Eat
Restaurant
Database
Do we really want to
build all this?
@crichardson
21. Use cloud-based services
• Highly scalable services
• Someone else’s headache to develop and maintain
• Provided by IaaS/PaaS
• Provided by 3rd party
@crichardson
23. Thousands of 3rd party services
http://www.slideshare.net/jmusser/j-musser-apishotnotgluecon2012
http://www.programmableweb.com/apis/directory
@crichardson
24. Cloud service trends
• Predominantly REST
• Predominantly JSON
•> billion API calls/day: Twitter, Google, Facebook, Netflix,
Accuweather, ...
• Increasing number of API-only companies
http://www.slideshare.net/jmusser/j-musser-apishotnotgluecon2012
@crichardson
26. Benefits of cloud services
• Someone else’s headache to develop and operate
• Focus on your core business problem
• Get up and running quickly
• Elasticity
• Capex Opex
@crichardson
27. Drawbacks of cloud services
• Complexity and drawbacks of a distributed system
• You are dependent on service provider
@crichardson
28. Risks of cloud services
Urban Airship’s Strategic Partnership With
SimpleGeo Turns Into An Acquisition
@crichardson
40. MongoDB
• Document-oriented database
• Very fast, highly scalable and available
• Rich
query language that supports location-
based queries
• Provided by CloudFoundry.com
@crichardson
42. Spring Data for MongoDB
• Provides MongoTemplate
• Analogous to JdbcTemplate
• Hides boilerplate code
• Domain object Document mapping
@crichardson
43. Using Spring data: creating an
index on location attribute
@Component
class MongoFriendService extends FriendService {
@Autowired
var mongoTemplate: MongoTemplate = _
@PostConstruct Collection name
def createGeoIndex {
val dbo = new BasicDBObject
dbo.put("location", "2d")
mongoTemplate.getCollection("friendRecord").ensureIndex(dbo)
}
Create geospatial 2d index
@crichardson
44. Using Spring Data: adding record
@Component
class MongoFriendService extends FriendService {
override def addOrUpdate(request: AddOrUpdateUserRequest) = {
val name = request.name
val phoneNumber = request.phoneNumber
val fr = new FriendRecord(phoneNumber, name,
new Point(request.longitude, request.latitude))
mongoTemplate.save(fr)
}
case class FriendRecord(id : String,
name : String,
location : Point)
@crichardson
45. Using Spring Data: finding nearby
friends
@Component
class MongoFriendService extends FriendService {
override def findNearbyFriends(request: NearbyFriendsRequest) = {
val location = new Point(request.longitude, request.latitude)
val distance = new Distance(3, Metrics.MILES)
val query = NearQuery.near(location).maxDistance(distance)
val result = mongoTemplate.geoNear(query, classOf[FriendRecord])
val nearby = result.getContent.map(_.getContent)
FindNearbyFriendsResponse(nearby.map(f => FriendInfo(f.name, f.id)))
}
@crichardson
47. Binding a service to an application
$ vmc push vme-user --path web/target/
Application Deployed URL [cer-spring.cloudfoundry.com]:
Detected a Java SpringSource Spring Application, is this correct? [Yn]:
Memory Reservation (64M, 128M, 256M, 512M, 1G) [512M]:
Creating Application: OK
Would you like to bind anyany services to 'vme-user'? [yN]: y
Would you like to bind services to 'vme-user'? [yN]: y
Would you like to use an an existing provisioned [yN]: y
Would you like to use existing provisioned service? service? [yN]: y
The The following provisioned services are available
following provisioned services are available
1: vme-mongo
1: vme-mongo
2: mysql-135e0
2: mysql-135e0
Please select one you wish to use: use: 1
Please select one you wish to 1
Binding Service [vme-mongo]: OK OK
Binding Service [vme-mongo]:
Uploading Application:
Checking for available resources: OK
Processing resources: OK
Packing application: OK
Uploading (12K): OK
Push Status: OK
@crichardson
50. Using Factual
• Geographic database as a Service
• Including 1.2M restaurants in the US
• Pricing: 10K calls day free, pay per use
@crichardson
51. Factual API
• RESTful/JSON interface
• Uses 2-legged OAuth 1.0.
• Geo and text filters
• Pagination
• Libraries for various languages
@crichardson
52. Restaurant Service
@Service
class FactualRestaurantService extends RestaurantService {
@Value("${factual_consumer_key}") var consumerKey: String = _
@Value("${factual_consumer_secret}") var consumerSecret: String = _
var factual: Factual = _
@PostConstruct
5 restaurants within 1km
def initialize {
factual = new Factual(consumerKey, consumerSecret, true)
}
override def findNearbyRestaurants(location: Location) = {
...
val restaurants = factual.get.fetch("restaurants-us",
new Query().within(new Circle(location.lat, location.lon, 1000)).limit(5))
val rs = restaurants.getData.map { map =>
RestaurantInfo(map.get("name").asInstanceOf[String])
}
FindNearbyRestaurantResponse(rs.toList)
}
...
@crichardson
53. Agenda
• Why use cloud services?
• Developing location-based applications
• Building SMS and telephony enabled applications
• Developing robust, fault tolerant applications
@crichardson
54. The telephony and SMS are
important
7/ waking hr !
http://blog.nielsen.com/nielsenwire/online_mobile/new-mobile-obsession-
Nielsen
u-s-teens-triple-data-usage/
@crichardson
57. VoteMeetEat.com & Telephony
• Handling registration SMS
• Sending SMS notifying users to vote
• Handling incoming voice call from voters:
• Text-to-speech of restaurants options
• Collecting digits entered via keypad
• Sending SMS/Voice notifications of voting results
@crichardson
58. DIY telephony = Difficult
aS:
y-a
hon
• Difficult to setup and operate
Telep
• Expensive
e S MS/
to us
ter
• Complex SMS protocols
Bet
•…
@crichardson
59. Telephony/SMS - aaS
• SMS • SMS
• Inbound and outgoing calls • Inbound and outgoing calls
• Recording and transcription • Recording and transcription
• Twitter
• IM
@crichardson
60. Twilio - Telephony and SMS as a
service
• REST API
• Allocate phone numbers
• Make and receive phone calls
• Send and receive SMS messages
• Pay per use:
• Phone calls - per-minute
• SMS – per SMS sent or received
• Phone number – per month
• Examples
• OpenVBX is a web-based, open source phone system
• StubHub – notifies sellers of a pending sale via phone
• SurveyMonkey – interactive polling
• Salesforce – SMS-based voting for 19,000 conference attendees
@crichardson
61. Using Twilio Manage resources
Send SMS
Initiate voice calls
REST API
Voice
SMS
Your
Twilio HTTP GET/ Application
POST
TwiML doc
Phone number
Handle incoming SMS and voice calls
SMS URL + VOICE URL Respond to user input
@crichardson
66. Inviting users to vote
POST /2010-04-01/Accounts/≪AccountSID≫/SMS/Messages
From=+15105551212
&To=+14155551212
&Body=≪MESSAGE≫
Authorization: Basic ....
Basic auth using Twilio
AccountSid+AuthToken
@crichardson
67. Sending SMS using the Spring
RestTemplate
@Component
class TwilioService {
def sendSms(recipient : String, message : String) = {
val response = postToTwilio("SMS/Messages",
Map("From" -> twilioPhoneNumber, "To" -> recipient, "Body" -> message))
(response "SMSMessage" "Sid").text
}
@crichardson
68. Sending SMS using the Spring
@Component
RestTemplate
class TwilioService {
TODO
def postToTwilio(resourcePath : String, requestParams : Map[String, String]) = {
val entity = makeEntity(requestParams)
try {
val response = restTemplate.postForObject(twilioUrl +
"/Accounts/{accountSid}/{resource}",
entity, classOf[String],
accountSid, resourcePath)
XML.loadString(response)
} catch {
case e : HttpClientErrorException if e.getStatusCode == HttpStatus.BAD_REQUEST =>
val body = e.getResponseBodyAsString()
val xmlBody = XML.loadString(body)
val code = Integer.parseInt((xmlBody "Code").text)
val message = (xmlBody "Message").text
throw new TwilioRestException(message, code)
}
}
@crichardson
70. Voting
HTTP POST
http://≪voiceUrl≫?From=≪PhoneNumber≫
Survey Management
Call <Response>
Twilio <Say> Chris would like to meet and eat. </Say>
<Gather action="handleresponse.html"
method="POST" numDigits="1">
<Say>Press 1 for ....</Say>
<Say>Press 2 for ....</Say>
</Gather>
</Response> @crichardson
71. Voting
HTTP POST
http://....handleresponse.html?
From=≪PhoneNumber≫&Digits=≪...≫
Survey Management
Digits
<Response>
Twilio <Say>Thank you for choosing.
The most popular place so far is ...
</Say>
<Pause/>
<Say>You will hear from us soon. Good bye</Say>
<Hangup/>
</Response>
@crichardson
72. Voting code 1
@Controller
class TwilioController {
@Autowired
var surveyManagementService: SurveyManagementService = _
@RequestMapping(value = Array("/begincall.html"))
@ResponseBody
def beginCall(@RequestParam("From") callerId: String) = {
surveyManagementService.findSurveyByCallerId(callerId) match {
case None =>
<Response>
<Say>Sorry don't recognize your number</Say>
<Hangup/>
</Response>
case Some(survey) =>
<Response>
<Say>{ survey.prompt }</Say>
<Gather action="handleresponse.html" method="POST" numDigits="1">
{
for ((choice, index) <- survey.choices zipWithIndex)
yield <Say>Press { index } for { choice }</Say>
}
</Gather>
<Say>We are sorry you could not decide</Say>
<Hangup/>
</Response>
} @crichardson
}
73. Voting code 2
class TwilioController {
...
@RequestMapping(value = Array("/handleresponse.html"))
@ResponseBody
def handleUserResponse(@RequestParam("From") callerId: String,
@RequestParam("Digits") digits: Int) = {
val survey = surveyManagementService.recordVote(callerId, digits)
<Response>
<Say>Thank you for choosing. The most popular place so far is
{ survey.map(_.mostPopularChoice) getOrElse "oops" }
</Say>
<Pause/>
<Say>You will hear from us soon. Good bye</Say>
<Hangup/>
</Response>
}
}
@crichardson
74. Agenda
• Why use cloud services?
• Developing location-based applications
• Building SMS and telephony enabled applications
• Developing robust, fault tolerant applications
@crichardson
75. The need for parallelism
Service B
b = serviceB()
Call in parallel
c = serviceC()
Service A Service C
d = serviceD(b, c)
Service D
@crichardson
76. Futures are a great concurrency
abstraction
• Object that will contain the result of a concurrent computation -
http://en.wikipedia.org/wiki/Futures_and_promises
• Various implementations
Future<Integer> result =
executorService.submit(new Callable<Integer>() {... });
• Java 7 Futures = ok
• Guava ListenableFutures = better
• Scala’s composable Futures = really good
• Java 8 CompletableFuture = great
@crichardson
77. Using futures to parallelize requests
trait FriendService {
def findNearbyFriends(request : NearbyFriendsRequest) :
Future[FindNearbyFriendsResponse]
}
trait RestaurantService {
Client
def findNearbyRestaurants(location: Location) :
Side Future[FindNearbyRestaurantResponse]
Proxies }
val f1 = friendsService.findNearbyFriends(NearbyFriendsRequest.fromLocation(vmeRecord.location))
val f2 = restaurantService.findNearbyRestaurants(vmeRecord.location)
val nearbyFriends = f1.get(2, TimeUnit.SECONDS)
val nearbyRestaurants = f2.get(2, TimeUnit.SECONDS)
Two calls execute concurrently
@crichardson
78. Using external web services =
Distributed system
Twilio
MongoDB
Mobile
Phone VoteMeetEat
Factual.Com
@crichardson
79. Internally = Distributed System
Survey
management
User
management
Registration
SMS
Rabbit
MQ
Registration
web app
VME
management
VME
web app
@crichardson
81. About Netflix
> 1B API calls/day
1 API call average 6 service calls
Fault tolerance is essential
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
@crichardson
82. Use network timeouts and retries
Never wait forever
Network errors can be transient retry
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
@crichardson
83. Use per-dependency bounded thread pool
Service A
Runnable 1 Task 1
Runnable 2 Task 2
Service B
Runnable ... Task ...
bounded queue bounded thread pool
Fails fast if Limits number of
service is slow or down outstanding requests
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
@crichardson
84. Use a circuit breaker
High error rate stop calling temporarily
Down wait for it to come back up
Slow gives it a chance to recover
Closed
errors Open
timeout
success Half fail
open http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
@crichardson
85. On failure
Return cached data
Avoid
Failing
Return default data
Fail fast
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
@crichardson
86. About Netflix Hystrix
• Open-source library from Netflix
• Implements class MyCommand
extends HystrixCommand[ResultType](...)
• Circuit Breaker pattern
override def run() = {
... Invoke remote service ...
• Bounded thread-pool }
}
• Fallback logic
val future = new MyCommand().queue()
...
• ...
• https://github.com/Netflix/Hystrix
@crichardson
87. Using Hystrix
@Service
class FactualRestaurantService extends RestaurantService {
@Autowired
@Qualifier("factualHystrixConfig")
var factualHystrixConfig: HystrixCommand.Setter = _
override def findNearbyRestaurants(location: Location) = {
class FindRestaurantsCommand
extends HystrixCommand[FindNearbyRestaurantResponse]
(factualHystrixConfig
.andCommandKey(HystrixCommandKey.Factory.asKey("FindRestaurantsCommand"))) {
override def run() = {
val restaurants = factual.fetch("restaurants",
new Query().within(new Circle(location.lat, location.lon, 1000)).limit(5))
val rs = for (map <- restaurants.getData) yield {
RestaurantInfo(map.get("name").asInstanceOf[String])
}
FindNearbyRestaurantResponse(rs.toList)
}
}
new FindRestaurantsCommand().queue()
}
} @crichardson
88. Summary
Cloud services are highly scalable services developed and
operated by a 3rd party
Let’s you focus on your core business problem
Risk: provider is acquired and stops offering service
Developing an application that reliably consumes cloud
services requires careful design
@crichardson