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
Presentation goal
How decomposing applications
improves deployability and
scalability
and
simplifies the adoption of new
technologies

@crichardson
(About Chris)

@crichardson
About Chris()

@crichardson
About Chris

http://www.theregister.co.uk/2009/08/19/springsource_cloud_foundry/

@crichardson
About Chris

@crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
API gateway design
Refactoring the monolith

@crichardson
Let’s imagine you are
building an online store

@crichardson
Traditional application
architecture
WAR/EAR

StoreFrontUI

Browser

Apache/
Load
balancer

Spring MVC

Product Info
Service
Recommendation
Service

Spring
Hibernate

MySQL
Database

Review Service

Simple to
develop
test
deploy
scale

Order Service

Tomcat

@crichardson
But there are problems with
a monolithic architecture

@crichardson
Users expect a rich,
dynamic and interactive
experience

@crichardson
Presentation layer evolution....
WAR

Browser

Old

HTML / HTTPre
tu
e
tyl
s

Ia
U

h
ug
o
StoreFrontUI en
d
oo
’t g
Controller
isn View

c
ite
rch

Model

@crichardson
...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
Intimidates developers

@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

Eggs in
one basket

Fear of change

Updates will happen less often - really long QA cycles
e.g. Makes A/B testing UI really difficult

@crichardson
Overloads your IDE and
container

Slows down development

@crichardson
Obstacle to scaling
development
Accounting
Engineering

E-commerce
application

Shipping team

@crichardson
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
Obstacle to scaling
development
I want
to update the UI

But
the backend is not working
yet!

Lots of coordination and
communication required

@crichardson
Requires long-term commitment
to a technology stack

@crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
API gateway design
Refactoring the monolith

@crichardson
@crichardson
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
Y-axis scaling - application level
WAR

StoreFrontUI
Product Info
Service
Recommendation
Service
Review
Service
Order
Service

@crichardson
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
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
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

@crichardson
Inter-service communication
options
Synchronous HTTP

asynchronous AMQP

Formats: JSON, XML, Protocol Buffers, Thrift, ...

Asynchronous is preferred but
REST is popular too
JSON is fashionable but binary format
is more efficient
@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
http:/
/oredev.org/2012/sessions/microservice-architecture

@crichardson
Real world examples
http://techblog.netflix.com/

http://highscalability.com/amazon-architecture

http://www.addsimplicity.com/downloads/
eBaySDForum2006-11-29.pdf
http://queue.acm.org/detail.cfm?id=1394128

@crichardson
There are drawbacks

@crichardson
Complexity
See Steve Yegge’s Google Platforms Rant re
Amazon.com
@crichardson
Multiple databases
&
Transaction management

@crichardson
Implementing features that
span multiple services

@crichardson
When to use it?
In the beginning:
•You don’t need it
•It will slow you down

Later on:
•You need it
•Refactoring is painful
@crichardson
But there are many benefits

@crichardson
Smaller, simpler apps

Easier to understand and develop
Reduced startup time - important for GAE
Less jar/classpath hell

Who needs OSGI?
@crichardson
Scales development:
develop, deploy and scale
each service independently
@crichardson
Improves fault isolation

@crichardson
Eliminates long-term commitment
to a single technology stack

Modular, polyglot, multiframework applications

@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
@crichardson
Easily try other technologies

... and fail safely
@crichardson
The human body as a system

@crichardson
50 to 70 billion of your cells die
each day

@crichardson
Yet you (the system) remain you

@crichardson
Can we build software systems
with these characteristics?
http://dreamsongs.com/Files/
DesignBeyondHumanAbilitiesSimp.pdf

http://dreamsongs.com/Files/WhitherSoftware.pdf

@crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
API gateway design
Refactoring the monolith

@crichardson
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
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
specific APIs

Protocol
translation

@crichardson
Optimized client-specific
APIs
Desktop
Browser

getProductInfo()
getRecomm...()
getReviews()

NodeJS
API
Gateway
REST
proxy

Mobile
App

getProductDetails()

Event
publishing

REST

Product Info
service

REST

Recommendation
Service

AMQP

Review
service

@crichardson
Netflix API Gateway
Device specific
end points

http://techblog.netflix.com/2013/01/optimizing-netflix-api.html

@crichardson
Developing an API gateway

Java EE web technologies
Netty-based technology stack
Non-Java options: e.g. Node.JS

@crichardson
API gateway design issues

@crichardson
The need for parallelism
Product Info
getProductDetails()

get
Product
Details

Product
Details
API

getRecomendations()

Recommendations

Call in
parallel

getReviews()

Reviews
@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.... @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
@crichardson
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
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
Handling partial failures
Product Info
getProductDetails()

get
Product
Details

Product
Details
Controller

getRecomendations()

getReviews()

X

Recommendations

Reviews
@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
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
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/Netflix/Hystrix
@crichardson
Agenda
The (sometimes evil) monolith
Decomposing applications into services
API gateway design
Refactoring the monolith

@crichardson
How do you decompose
your big, scary monolithic
application?

@crichardson
Strategy #1: Stop digging

@crichardson
New functionality = service
Pristine

Monolith

Anti-corruption
layer

Service

Glue code
@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
...

@crichardson
Strategy #2: Split front-end
& backend

@crichardson
Split front-end & backend
Independently
deployable
Monolith
Presentation
layer

Front-end

Backend

Backend

@crichardson
Strategy #3: Decompose
front-end

@crichardson
Decompose front-end
Front-end
Catalog

/catalog
Catalog

Checkout

/checkout
Checkout

Account

/account
Account

Orders

/orders
Orders
@crichardson
Issues and challenges

Server-side Session state:
Need to use a separate session state server
...

@crichardson
Strategy #4: extract services

@crichardson
Module

service ...

WAR
Module

@crichardson
... Module

WAR

service

Anti-corruption
layer

Service

@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

@crichardson
Untangling dependencies
Monolith

Service

API A

API C

API B

Trouble!
Domain model 1
X

Domain model 2
C

A

Y

B
Z
@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
@crichardson
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
Replicating the credit limit
Order Service

sum(order.total) <=
creditLimit

placeOrder()

Order management

updateCustomer()

Customer management
Customer

Order
Simplified

Customer Service

creditLimit
...

total
CreditLimitChangedEvent

Customer’
creditLimit
@crichardson
Maintaining the openOrderTotal
Customer Service

Order Service
placeOrder()

= creditLimit - openOrderTotal

Order management

availableCredit()

Customer management
Customer

Order

creditLimit
openOrderTotal
...

customerId
total

OpenOrderTotalUpdated

@crichardson
Refactoring a monolith is not easy
BUT
the alternative is far worse
@crichardson
Summary

@crichardson
Monolithic applications are
simple to develop and deploy
BUT have significant
drawbacks
@crichardson
Apply the scale cube
Modular, polyglot, and
scalable applications
Services developed,
deployed and scaled
independently

@crichardson
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
@crichardson

chris@chrisrichardson.net

Questions?

http://plainoldobjects.com

@crichardson

Decompose That WAR! Architecting for Adaptability, Scalability, and Deployability (jmaghreb, jmaghreb2013)