This document summarizes a presentation about decomposing monolithic applications into microservices. The presentation discusses how monolithic architectures can be difficult to deploy, scale, and update. It then introduces strategies for decomposing a monolith into independent services, such as extracting new functionality or splitting the frontend from the backend. The presentation also covers best practices for service communication, including using an API gateway and futures to handle asynchronous requests. Overall, the document advocates for a modular, polyglot architecture using independent, interoperable services to improve deployability, scalability, and adaptability compared to a monolithic application.
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
Â
Decompose That WAR! Architecting for Adaptability, Scalability, and Deployability (jmaghreb, jmaghreb2013)
1. 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
@crichardson
2. Presentation goal
How decomposing applications
improves deployability and
scalability
and
simpliïŹes the adoption of new
technologies
@crichardson
13. ...Presentation layer evolution
Browser - desktop and mobile
View
Web application
Controller
Model
Static
content
JSON-REST
HTML 5 - JavaScript
Native mobile client
IoS or Android
Events
RESTful
Endpoints
Event
publisher
No elaborate, server-side web framework
required
@crichardson
15. Obstacle to frequent
deployments
Need to redeploy everything to change one component
Interrupts long running background (e.g. Quartz) jobs
Increases risk of failure
Eggs in
one basket
Fear of change
Updates will happen less often - really long QA cycles
e.g. Makes A/B testing UI really difïŹcult
@crichardson
18. Obstacle to scaling
development
WAR
UI team
StoreFrontUI
Product Info team
Product Info service
Recommendations team
Recommendation service
Reviews team
Review service
Orders team
Order service
@crichardson
19. Obstacle to scaling
development
I want
to update the UI
But
the backend is not working
yet!
Lots of coordination and
communication required
@crichardson
23. The scale cube
X axis
- horizontal duplication
Sc
Z
ax
is
Scale by
splitting
different things
ale - d
by ata
sp pa
th litt rtit
in in ion
gs g
sim ing
ila
r
Y axis functional
decomposition
@crichardson
24. Y-axis scaling - application level
WAR
StoreFrontUI
Product Info
Service
Recommendation
Service
Review
Service
Order
Service
@crichardson
25. Y-axis scaling - application level
product info application
Product Info
Service
recommendations application
Store front application
StoreFrontUI
Recommendation
Service
reviews application
Review
Service
orders application
Order
Service
Apply X axis cloning and/or Z axis partitioning to each service
@crichardson
26. Partitioning strategies...
Partition by noun, e.g. product info service
Partition by verb, e.g. shipping service
Single Responsibility Principle
Unix utilities - do one focussed thing well
@crichardson
27. 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 difïŹcult to understand system
Something of an art
@crichardson
29. 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
http:/
/oredev.org/2012/sessions/microservice-architecture
@crichardson
37. Smaller, simpler apps
Easier to understand and develop
Reduced startup time - important for GAE
Less jar/classpath hell
Who needs OSGI?
@crichardson
41. 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
@crichardson
46. Can we build software systems
with these characteristics?
http://dreamsongs.com/Files/
DesignBeyondHumanAbilitiesSimp.pdf
http://dreamsongs.com/Files/WhitherSoftware.pdf
@crichardson
47. Agenda
The (sometimes evil) monolith
Decomposing applications into services
API gateway design
Refactoring the monolith
@crichardson
48. Directly connecting the front-end to the backend
Chatty API
View
REST
Product Info
service
REST
Recommendation
Service
AMQP
Controller
Review
service
Model
Traditional web
application
View
Controller
Model
Browser/Native App
Web unfriendly
protocols
@crichardson
49. Use an API gateway
Single entry point
View
REST
Product Info
service
REST
Recommendation
Service
AMQP
Controller
Review
service
Model
Traditional web
application
View
API
Gateway
Controller
Model
Browser/Native App
Client
speciïŹc APIs
Protocol
translation
@crichardson
54. The need for parallelism
Product Info
getProductDetails()
get
Product
Details
Product
Details
API
getRecomendations()
Recommendations
Call in
parallel
getReviews()
Reviews
@crichardson
55. 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.... @crichardson
56. 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
@crichardson
57. Even better: Composable Futures
val f1 = Future {
val f2 = Future {
... ; 1 }
... ; 2 }
Transforms Future
val f4 = f2.map(_ * 2)
assertEquals(4, Await.result(f4, 1 second))
Combines two futures
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)) Transforms list of futures to a
future containing a list
Scala Futures
@crichardson
58. 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
@crichardson
59. Handling partial failures
Product Info
getProductDetails()
get
Product
Details
Product
Details
Controller
getRecomendations()
getReviews()
X
Recommendations
Reviews
@crichardson
60. About NetïŹix
> 1B API calls/day
1 API call
average 6 service calls
Fault tolerance is essential
http://techblog.netïŹix.com/2012/02/fault-tolerance-in-high-volume.html
@crichardson
61. How to run out of threads
Thread 1
X
HTTP Request
Thread 2
X
X
Thread 3
API
Gateway
Code
X
Recommendation
Service
Thread n
X
Execute thread
pool
Tomcat
Eventually all threads
will be blocked
If service is down
then thread will
be blocked
@crichardson
62. Their approach
Network timeouts
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/NetïŹix/Hystrix
@crichardson
63. Agenda
The (sometimes evil) monolith
Decomposing applications into services
API gateway design
Refactoring the monolith
@crichardson
64. How do you decompose
your big, scary monolithic
application?
@crichardson
66. New functionality = service
Pristine
Monolith
Anti-corruption
layer
Service
Glue code
@crichardson
67. 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
...
@crichardson
76. 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
ConïŹicting resource requirements
@crichardson
78. 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
@crichardson
79. Untangling orders and customers
Customer Service
Order Service
availableCredit()
updateCustomer()
placeOrder()
Order management
Order
Customer management
belongs to
has orders
creditLimit
...
total
Invariant:
sum(order.total) <= creditLimit
Customer
Trouble!
available credit= creditLimit sum(order.total)
@crichardson
80. Replicating the credit limit
Order Service
sum(order.total) <=
creditLimit
placeOrder()
Order management
updateCustomer()
Customer management
Customer
Order
SimpliïŹed
Customer Service
creditLimit
...
total
CreditLimitChangedEvent
Customerâ
creditLimit
@crichardson
81. Maintaining the openOrderTotal
Customer Service
Order Service
placeOrder()
= creditLimit - openOrderTotal
Order management
availableCredit()
Customer management
Customer
Order
creditLimit
openOrderTotal
...
customerId
total
OpenOrderTotalUpdated
@crichardson
85. Apply the scale cube
Modular, polyglot, and
scalable applications
Services developed,
deployed and scaled
independently
@crichardson
86. Use a modular, polyglot architecture
View
REST
Controller
Product Info
service
REST
Recommendation
Service
AMQP
Review
service
Model
Traditional web
application
View
Controller
API
Gateway
Model
@crichardson