It’s no longer acceptable to develop large, monolithic, single-language, single-framework Web applications. In this session, you will learn how to use the scale cube to decompose your monolithic Web application into a set of narrowly focused, independently deployable services. The presentation discusses how a modular architecture makes it easy to adopt newer and better languages and technologies. You will learn about the various communication mechanisms—synchronous and asynchronous—that these services can use.
Decompose That WAR! Architecting for Adaptability, Scalability, and Deployability
1. @crichardson
Decompose That WAR!
Architecting for Adaptability,
Scalability, and Deployability
Chris Richardson
Author of POJOs in Action
Founder of the original CloudFoundry.com
@crichardson
chris@chrisrichardson.net
http://plainoldobjects.com
9. @crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
How do services communicate?
API gateway design
Refactoring the monolith
15. @crichardson
Browser - desktop and mobile Web application
RESTful
Endpoints
Model
View Controller
...Presentation layer evolution
JSON-REST
HTML 5 - JavaScript
Native mobile client
IoS or Android
Event
publisher
Events
Static
content
No elaborate, server-side web framework
required
17. @crichardson
Obstacle to frequent
deployments
Need to redeploy everything to change one component
Interrupts long running background (e.g. Quartz) jobs
Increases risk of failure
Fear of change
Updates will happen less often - really long QA cycles
e.g. Makes A/B testing UI really difficult
Eggs in
one basket
20. @crichardson
WAR
Review service
Product Info service
Recommendation service
StoreFrontUI
Reviews team
Product Info team
Recommendations team
UI team
Obstacle to scaling
development
Order serviceOrders team
21. @crichardson
Lots of coordination and
communication required
Obstacle to scaling
development
I want
to update the UI
But
the backend is not working
yet!
23. @crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
How do services communicate?
API gateway design
Refactoring the monolith
25. @crichardson
The scale cube
X axis
- horizontal duplication
Z
axis
-data
partitioning
Y axis -
functional
decomposition
Scale
by
splitting
sim
ilar
things
Scale by
splitting
different things
26. @crichardson
Y-axis scaling - application level
WAR
Review
Service
Product Info
Service
Recommendation
Service
StoreFrontUI
Order
Service
27. @crichardson
Y-axis scaling - application level
Store front web application
reviews web application
recommendations web application
Apply X axis cloning and/or Z axis partitioning to each service
product info web application
Review
Service
Product Info
Service
Recommendation
Service
StoreFrontUI
Order
Service
orders web application
29. @crichardson
Partitioning strategies
Too few
Drawbacks of the monolithic architecture
Too many - a.k.a. Nano-service anti-pattern
Runtime overhead
Potential risk of excessive network hops
Potentially difficult to understand system
Something of an art
30. @crichardson
Example micro-service
class RegistrationSmsServlet extends RegistrationSmsScalatraStack {
post("/") {
val phoneNumber = request.getParameter("From")
val registrationUrl = System.getenv("REGISTRATION_URL") +
"?phoneNumber=" + encodeForUrl(phoneNumber)
<Response>
<Sms>To complete registration please go to {registrationUrl}</Sms>
</Response>
}
}
For more on micro-services see
@fgeorge52
42. @crichardson
Two levels of architecture
System-level
Services
Inter-service glue: interfaces and communication mechanisms
Slow changing
Service-level
Internal architecture of each service
Each service could use a different technology stack
Pick the best tool for the job
Rapidly evolving
44. @crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
How do services communicate?
API gateway design
Refactoring the monolith
47. Pros and cons of messaging
Pros
Decouples client from
server
Message broker buffers
messages
Supports a variety of
communication patterns
Cons
Additional complexity of
message broker
Request/reply-style
communication is more
complex
48. Spring Integration
Provides the building blocks for a pipes
and filters architecture
Enables development of application
components that are
loosely coupled
insulated from messaging infrastructure
Messaging defined declaratively
52. Pros and cons of REST
Pros
Simple and familiar
Request/reply is easy
Firewall friendly
No intermediate broker
Cons
Only supports request/
reply
Server must be
available
Client needs to know
URL(s) of server(s)
54. @crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
How do services communicate?
API gateway design
Refactoring the monolith
55. @crichardson
Use an API gateway
Browser
Model
View Controller
HTML 5 - JavaScript
Product Info
service
Recommendation
Service
Review
service
REST
REST
AMQP
API
Gateway
Model
View Controller
Native App
Single entry point
Client
specific APIs
Protocol
translation
59. @crichardson
The need for parallelism
Product
Details
API
Product Info
Recommendations
Reviews
getProductDetails()
getRecomendations()
getReviews()
Call in
parallel
get
Product
Details
60. @crichardson
Futures are a great
concurrency abstraction
An object that will contain the result of a concurrent
computation
http://en.wikipedia.org/wiki/Futures_and_promises
Future<Integer> result =
executorService.submit(new Callable<Integer>() {... });
Java has basic futures. We
can do much better....
61. @crichardson
Better: Futures with callbacks
val f : Future[Int] = Future { ... }
f onSuccess {
case x : Int => println(x)
}
f onFailure {
case e : Exception => println("exception thrown")
}
Guava ListenableFutures,
Java 8 CompletableFuture, Scala Futures
62. @crichardson
Even better: Composable Futures
val f1 = Future { ... ; 1 }
val f2 = Future { ... ; 2 }
val f4 = f2.map(_ * 2)
assertEquals(4, Await.result(f4, 1 second))
val fzip = f1 zip f2
assertEquals((1, 2), Await.result(fzip, 1 second))
def asyncOp(x : Int) = Future { x * x}
val f = Future.sequence((1 to 5).map { x => asyncOp(x) })
assertEquals(List(1, 4, 9, 16, 25),
Await.result(f, 1 second))
Scala Futures
Transforms Future
Combines two futures
Transforms list of futures to a
future containing a list
63. @crichardson
Composing concurrent requests
using Scala Futures
class ProductDetailsService @Autowired()(....) {
def getProductDetails(productId: Long) = {
val productInfoFuture = productInfoService.getProductInfo(productId)
val recommendationsFuture =
recommendationService.getRecommendations(productId)
val reviewsFuture = reviewService.getReviews(productId)
for (((productInfo, recommendations), reviews) <-
productInfoFuture zip recommendationsFuture zip
reviewsFuture)
yield ProductDetails(productInfo, recommendations, reviews)
}
}
Asynchronously creates a Future containing result
65. @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
66. @crichardson
How to run out of threads
Tomcat
Execute thread
pool
HTTP Request
Thread 1
Thread 2
Thread 3
Thread n
Service A Service B
If service B is
down then thread
will be blocked
XX
X
X
X
Eventually all threads
will be blocked
67. @crichardson
Their approach
Network timeouts and retries
Invoke remote services via a bounded thread pool
Use the Circuit Breaker pattern
On failure:
return default/cached data
return error to caller
https://github.com/Netflix/Hystrix
68. @crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
How do services communicate?
API gateway design
Refactoring the monolith
72. @crichardson
Sounds simple but...
Dependencies between monolith and service
e.g. Common entities
Building the anti-corruption layer can be challenging
Must replicate data between systems
...
76. @crichardson
What to extract?
Have the ideal partitioned architecture in mind:
Partitioned by verb or noun
Start with troublesome modules:
Frequently updated
Stateful components that prevent clustering
Conflicting resource requirements
78. @crichardson
Useful idea: Bounded context
Different services have a different view of a
domain object, e.g.
User Management = complex view of user
Rest of application: User = PK + ACL + Name
Different services can have a different domain
model
Services exchange messages to synchronize data
79. @crichardson
Customer management
Untangling orders and customers
Order management
Order Service
placeOrder()
Customer Service
availableCredit()
updateCustomer()
Customer
creditLimit
...
has ordersbelongs toOrder
total
Invariant:
sum(order.total) <= creditLimit
available credit= creditLimit -
sum(order.total)
Trouble!
80. @crichardson
Customer management
Replicating the credit limit
Order management
Order Service
placeOrder()
Customer
creditLimit
...
Order
total
Customer’
creditLimit
CreditLimitChangedEvent
sum(order.total) <=
creditLimit
Customer Service
updateCustomer()
Simplified
81. @crichardson
Customer management
Maintaining the openOrderTotal
Order management
Order Service
placeOrder()
Customer Service
availableCredit()
Customer
creditLimit
...
Order
customerId
total
= creditLimit - openOrderTotal
OpenOrderTotalUpdated
openOrderTotal
85. @crichardson
Apply the scale cube
Modular, polyglot, and
scalable applications
Services developed,
deployed and scaled
independently
86. @crichardson
Use a modular, polyglot architecture
Browser
Model
View Controller
HTML 5 - JavaScript
Product Info
service
Recommendation
Service
Review
service
REST
REST
AMQP
Front-end
server
Model
View Controller
Native App
API
Gateway
Event
delivery