developing applications with cloud services (devnexus 2013)
DESCRIPTION
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.TRANSCRIPT
DEVELOPING WITH CLOUD SERVICES
Chris Richardson
Author of POJOs in ActionFounder of the original CloudFoundry.com
@[email protected]://plainoldobjects.com
@crichardson
vmc push About-Chris
Developer Advocate for CloudFoundry.com
Signup at http://cloudfoundry.com
@crichardson
Agenda
• Why use cloud services?
• Developing location-based applications
• Building SMS and telephony enabled applications
• Developing robust, fault tolerant applications
@crichardson
Three phases of every galactic civilization
How
Why
Where
can we eat?
do we eat?
Where shall we have lunch?
@crichardson
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
VoteMeetEat.com
Friend and restaurant location databases+
SMS +
Voice callsTo sign up text
"register" to 510-XXX-YYYY
@crichardson
High-level architecture
VoteMeetEat
TelephonyIntegration
Friend GeoDatabase
Restaurant Database
MobilePhone
Do we really want to build all this?
DIY = DIFFICULT
@crichardson
Use cloud-based services
• Highly scalable services
• Someone else’s headache to develop and maintain
• Provided by IaaS/PaaS
• Provided by 3rd party
@crichardson
Thousands of 3rd party services
http://www.programmableweb.com/apis/directory
http://www.slideshare.net/jmusser/j-musser-apishotnotgluecon2012
@crichardson
• 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
Cloud service trends
@crichardson
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
Drawbacks of cloud services
• Complexity and drawbacks of a distributed system
• You are dependent on service provider
@crichardson
Risks of cloud services
Urban Airship’s Strategic Partnership With SimpleGeo Turns Into An Acquisition
@crichardson
Agenda
• Why use cloud services?
• Developing location-based applications
• Building SMS and telephony enabled applications
• Developing robust, fault tolerant applications
@crichardson
Lots of really difficult problems
•Scalable, spatial database – CRUD records, find nearby•Data management – database of places, street information•Forward geo-coding: address ⇒ lat/lon
•Reverse geo-coding: lat/lon ⇒ address
•Maps•Directions
Easier to use Geo-aaS
@crichardson
Examples of Geo-aaS
Beware the terms of service
• Maps• Forward and reverse geocoding• Directions• Elevation• Places
• Freely available geographic database
• Various APIs including reverse geocoding
• Business+review database• Neighborhood database
• Places database• Reverse geocoding
@crichardson
VoteMeetEat.com & Geo
trait FriendService { def addOrUpdate(request : AddOrUpdateUserRequest) def findNearbyFriends(request : NearbyFriendsRequest) :
FindNearbyFriendsResponse}
trait RestaurantService { def findNearbyRestaurants(location: Location) :
FindNearbyRestaurantResponse}
@crichardson
trait FriendService { def addOrUpdate(request : AddOrUpdateUserRequest) def findNearbyFriends(request : NearbyFriendsRequest) :
FindNearbyFriendsResponse}
Implementing the friends database
@crichardson
MongoDB
• Document-oriented database
• Very fast, highly scalable and available
• Rich query language that supports location-based queries
• Provided by CloudFoundry.com
@crichardson
MongoDB server
Database: VoteMeetEat
Collection: friendRecord
Storing friends in MongoDB
{ "_id": "+15105551212", "name": "Chris R.", "location": { "x": -122.25206103187264, "y": 37.847427441773796 }}
@crichardson
Spring Data for MongoDB
• Provides MongoTemplate
• Analogous to JdbcTemplate
• Hides boilerplate code
• Domain object ↔ Document mapping
@crichardson
Using Spring data: creating an index on location attribute
@Componentclass MongoFriendService extends FriendService {
@Autowired var mongoTemplate: MongoTemplate = _
@PostConstruct def createGeoIndex { val dbo = new BasicDBObject dbo.put("location", "2d") mongoTemplate.getCollection("friendRecord").ensureIndex(dbo) }
Create geospatial 2d index
Collection name
@crichardson
Using Spring Data: adding record@Componentclass 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
Using Spring Data: finding nearby friends
@Componentclass 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
$ 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: OKWould you like to bind any services to 'vme-user'? [yN]: y
Would you like to use an existing provisioned service? [yN]: yThe following provisioned services are available
1: vme-mongo2: mysql-135e0Please select one you wish to use: 1
Binding Service [vme-mongo]: OKUploading Application:
Checking for available resources: OK Processing resources: OK
Packing application: OK Uploading (12K): OK
Push Status: OK
Binding a service to an application
Would you like to bind any services to 'vme-user'? [yN]: yWould you like to use an existing provisioned service? [yN]: yThe following provisioned services are available1: vme-mongo2: mysql-135e0Please select one you wish to use: 1Binding Service [vme-mongo]: OK
@crichardson
Connecting to MongoDB <bean id="mongoTemplate"
class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg ref="mongoFactory" /> </bean>
<beans profile="default"> <mongo:db-factory id="mongoFactory" dbname="surveygeo" /> </beans>
<beans profile="cloud"> <cloud:mongo-db-factory id="mongoFactory" /> </beans>
Outside of Cloud Foundry
Inside Cloud Foundry
@crichardson
Implementing the restaurant database
trait RestaurantService { def findNearbyRestaurants(location: Location) : FindNearbyRestaurantResponse}
@crichardson
Using Factual
• Geographic database as a Service
• Including 1.2M restaurants in the US
• Pricing: 10K calls day free, pay per use
@crichardson
Factual API
• RESTful/JSON interface
• Uses 2-legged OAuth 1.0.
• Geo and text filters
• Pagination
• Libraries for various languages
@crichardson
Restaurant Service@Serviceclass FactualRestaurantService extends RestaurantService {
@Value("${factual_consumer_key}") var consumerKey: String = _ @Value("${factual_consumer_secret}") var consumerSecret: String = _
var factual: Factual = _
@PostConstruct 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)
}...
5 restaurants within 1km
@crichardson
Agenda
• Why use cloud services?
• Developing location-based applications
• Building SMS and telephony enabled applications
• Developing robust, fault tolerant applications
@crichardson
The telephony and SMS are important
http://blog.nielsen.com/nielsenwire/online_mobile/new-mobile-obsession-u-s-teens-triple-data-usage/
7/ waking hr !
Nielsen
@crichardson
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
DIY telephony = Difficult
• Difficult to setup and operate
• Expensive
• Complex SMS protocols
• …Bette
r to use S
MS/Telepho
ny-aaS:
@crichardson
Telephony/SMS - aaS
• SMS• Inbound and outgoing calls• Recording and transcription
• SMS• Inbound and outgoing calls• Recording and transcription• Twitter• IM
@crichardson
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
Using Twilio
Twilio Your Application
TwiML doc
HTTP GET/POST
REST API
Manage resourcesSend SMS
Initiate voice calls
Handle incoming SMS and voice callsRespond to user input
VoiceSMS
Phone number ⇒
SMS URL + VOICE URL
@crichardson
Handling SMS registration
TwilioSMS
REGISTRATION
HTTP POST http://≪smsUrl≫?From=≪PhoneNumber≫
<Response> <Sms>To complete registration please go to http://... </Sms></Response>
SMS
@crichardson
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
Sending SMS using the Spring RestTemplate
@Componentclass TwilioService {
def sendSms(recipient : String, message : String) = { val response = postToTwilio("SMS/Messages", Map("From" -> twilioPhoneNumber, "To" -> recipient, "Body" -> message)) (response \ "SMSMessage" \ "Sid").text }
@crichardson
Sending SMS using the Spring RestTemplate
TODO
@Componentclass TwilioService {
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
Voting
Twilio
Survey Management<Response> <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>
HTTP POST http://≪voiceUrl≫?From=≪PhoneNumber≫
Call
@crichardson
Voting
Twilio
Survey Management
<Response> <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>
HTTP POST http://....handleresponse.html?From=≪PhoneNumber≫&Digits=≪...≫
Digits
@crichardson
Voting code 1@Controllerclass 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
Voting code 2class 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
Agenda
• Why use cloud services?
• Developing location-based applications
• Building SMS and telephony enabled applications
• Developing robust, fault tolerant applications
@crichardson
The need for parallelism
Service A
Service B
Service C
Service D
b = serviceB()
c = serviceC()
d = serviceD(b, c)
Call in parallel
@crichardson
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
• Java 7 Futures = ok
• Guava ListenableFutures = better
• Scala’s composable Futures = really good
• Java 8 CompletableFuture = great
Future<Integer> result = executorService.submit(new Callable<Integer>() {... });
@crichardson
Using futures to parallelize requeststrait FriendService { def findNearbyFriends(request : NearbyFriendsRequest) :
Future[FindNearbyFriendsResponse]}
trait RestaurantService { def findNearbyRestaurants(location: Location) :
Future[FindNearbyRestaurantResponse]}
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
Client Side
Proxies
@crichardson
Using external web services = Distributed system
VoteMeetEat
TwilioMongoDB
Factual.Com
MobilePhone
@crichardson
Internally = Distributed System
Survey management
VMEmanagement
Registration SMS
Registrationweb app
VME web app
Usermanagement
RabbitMQ
@crichardson
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
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
Service A
Service B
Use per-dependency bounded thread pool
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
Runnable 1
Runnable 2
Runnable ...
bounded queue
Task 1
Task 2
Task ...
bounded thread pool
Limits number of outstanding requests
Fails fast if service is slow or down
@crichardson
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
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
Closed Open
Half open
errors
successtimeout
fail
@crichardson
On failure
http://techblog.netflix.com/2012/02/fault-tolerance-in-high-volume.html
AvoidFailing
Return cached data
Return default data
Fail fast
@crichardson
About Netflix Hystrix
• Open-source library from Netflix
• Implements
• Circuit Breaker pattern
• Bounded thread-pool
• Fallback logic
• ...
• https://github.com/Netflix/Hystrix
class MyCommand extends HystrixCommand[ResultType](...)
override def run() = { ... Invoke remote service ... }}
val future = new MyCommand().queue()...
@crichardson
@Serviceclass 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() }}
Using Hystrix
@crichardson
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
Questions?
@crichardson [email protected]://plainoldobjects.com - code and slides
Sign up for CloudFoundry.com