SlideShare a Scribd company logo
Ignasi Marimon-Clos - @ignasi35
(based on slides by Markus Jura - @markusjura)
Microservices “Just Right”
Lagom Workshop  BarcelonaJUG 2017-06-08
Disclaimer
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom - [lah-gome]
Adequate, sufficient, just right
• Overview
• Developer experience
• Service API
• Persistence & Clustering
• Running in production
TOC
Overview
• Reactive
• Built-in resiliency & failure handling patterns
• Developer experience
• No setup of external services
• Intra-service communication just works
• Services automatically reload on code change
• Takes you through to production deployment
Why Lagom?
• Java 8
• Play 2.5
• Akka 2.4 (Clustering, Streams, Persistence)
• Cassandra (default data store)
• Jackson (JSON serialization)
• Guice (DI)
Under the hood
Developer experience
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
• Ocado rip-off
• BasketService: CreateBasket, AddItem, RemoveItem
• InventoryService: AddItem, ProvisionItem, ReserveItem, PickUpItem
• OrderService: PlaceOrder, CompleteOrder, FailOrder
• DeliveryService: DeliverOrder
Today’s workshop
BasketService
OrderService
InventoryService
DeliveryService
Lagom Workshop  BarcelonaJUG 2017-06-08
Enough slides,
Demo time
$ sbt runAll
$ mvn lagom:runAll
Service API
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Service definition
public interface BasketService extends Service {
@Override
default Descriptor descriptor() {
return named("basketservice").withCalls(
namedCall("/api/baskets", this::createBasket)
)
}
ServiceCall<Basket, String> createBasket();
}
Service definition
public interface BasketService extends Service {
@Override
default Descriptor descriptor() {
return named("basketservice").withCalls(
restCall(Method.POST,
”/api/baskets”,this::createBasket)
)
}
ServiceCall<Basket, String> createBasket();
}
Each service definition is split into two sbt projects: api &
impl
Anatomy Lagom project
my-lagom-system ! Project root

# helloworld-api ! helloworld api project

# helloworld-impl ! helloworld implementation project

# project ! sbt configuration files

# plugins.sbt ! sbt plugins

# build.sbt ! Your project build file
Service definition
public interface FriendService extends Service {
@Override
default Descriptor descriptor() {
return named("friendservice").withCalls(
restCall(Method.POST,
”/api/users”,this::createUser)
)
}
ServiceCall<User, String> createUser();
}
Service definition
public interface FriendService extends Service {
@Override
default Descriptor descriptor() {
return named("friendservice").withCalls(
pathCall(”/api/users”,this::createUser)
)
}
ServiceCall<User, String> createUser();
}
Service definition
// this source is placed in your api project
public interface FriendService extends Service {
@Override
default Descriptor descriptor() {
return named("friendservice").withCalls(
namedCall(”createUser", this::createUser)
)
}
ServiceCall<User, String> createUser();
}
Service definition
// this source is placed in your api project
public interface FriendService extends Service {
@Override
default Descriptor descriptor() {
return named("friendservice").withCalls(
call(this::createUser)
)
}
ServiceCall<User, String> createUser();
}
Lagom Workshop  BarcelonaJUG 2017-06-08
$ sbt runAll
$ mvn lagom:runAll
$ sbt runAll
[info] Loading project definition from /Users/ignasi/workshop/hello/project
[info] Set current project to hello-workshop (in build file:/Users/ignasi/workshop/hello/)
[info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-kafka...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Starting Kafka
[info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-cassandra...
[info] Resolving com.google.guava#guava;18.0 ...
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Starting Cassandra
..........
[info] Cassandra server running at 127.0.0.1:4000
[info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-service-locator...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Service locator is running at http://localhost:8000
[info] Service gateway is running at http://localhost:9000
[info] Compiling 3 Java sources to /Users/ignasi/workshop/hello/hello-api/target/scala-2.11/classes...
[info] Compiling 1 Java source to /Users/ignasi/workshop/hello/hello-stream-api/target/scala-2.11/classes...
[info] Compiling 4 Java sources to /Users/ignasi/workshop/hello/hello-stream-impl/target/scala-2.11/classes...
[info] Compiling 6 Java sources to /Users/ignasi/workshop/hello/hello-impl/target/scala-2.11/classes...
[warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 1 : {hello-
events=LEADER_NOT_AVAILABLE}
[warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 2 : {hello-
events=LEADER_NOT_AVAILABLE}
[info] Service hello-impl listening for HTTP on 0:0:0:0:0:0:0:0:57797
[info] Service hello-stream-impl listening for HTTP on 0:0:0:0:0:0:0:0:58322
[info] (Services started, press enter to stop and go back to the console...)
curl http://localhost:9000/api/hello/Alice
$ sbt runAll
[info] Loading project definition from /Users/ignasi/workshop/hello/project
[info] Set current project to hello-workshop (in build file:/Users/ignasi/workshop/hello/)
[info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-kafka...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Starting Kafka
[info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-cassandra...
[info] Resolving com.google.guava#guava;18.0 ...
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Starting Cassandra
..........
[info] Cassandra server running at 127.0.0.1:4000
[info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-service-locator...
[info] Resolving jline#jline;2.12.1 ...
[info] Done updating.
[info] Service locator is running at http://localhost:8000
[info] Service gateway is running at http://localhost:9000
[info] Compiling 3 Java sources to /Users/ignasi/workshop/hello/hello-api/target/scala-2.11/classes...
[info] Compiling 1 Java source to /Users/ignasi/workshop/hello/hello-stream-api/target/scala-2.11/classes...
[info] Compiling 4 Java sources to /Users/ignasi/workshop/hello/hello-stream-impl/target/scala-2.11/classes...
[info] Compiling 6 Java sources to /Users/ignasi/workshop/hello/hello-impl/target/scala-2.11/classes...
[warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 1 : {hello-
events=LEADER_NOT_AVAILABLE}
[warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 2 : {hello-
events=LEADER_NOT_AVAILABLE}
[info] Service hello-impl listening for HTTP on 0:0:0:0:0:0:0:0:57797
[info] Service hello-stream-impl listening for HTTP on 0:0:0:0:0:0:0:0:58322
[info] (Services started, press enter to stop and go back to the console...)
curl http://localhost:9000/api/hello/Alice
Service definition
// this source is placed in your api project
public interface FriendService extends Service {
@Override
default Descriptor descriptor() {
return named("friendservice").withCalls(
namedCall(”createUsers", this::createUsers)
)
}
ServiceCall< Source<User,…> , …> createUsers();
}
$ git clone
https://github.com/ignasi35/lagom-java-workshop.git
ex001-start
Exercise
• Create service descriptor for Basket Service
ex001-start
Service definition
public interface SearchService extends Service {
@Override
default Descriptor descriptor() {
return named("search").withCalls(
pathCall(
”/api/search?pageNo&pageSize“,
this::search)
)
}
ServiceCall<SearchRequest, PageSeq<Result>>
search(int pageNo, int pageSize);
}
… pet/findByStatus”:{“get":{"tags":["pet"],"summary":"Finds Pets by
status","description":"Multiple status values can be provided with comma separated
strings","operationId":"findPetsByStatus","produces":["application/xml","application/
json"],"parameters":[{"name":"status","in":"query","description":"Status values that need to
be considered for filter","required":true,"type":"array","items":{"type":"string","enum":
["available","pending","sold"],"default":"available"},"collectionFormat":"multi"}],"responses
":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/
definitions/Pet"}}},"400":{"description":"Invalid status value"}},"security":
[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags":
["pet"],"summary":"Finds Pets by tags","description":"Muliple tags can be provided with comma
separated strings. Use tag1, tag2, tag3 for
testing.","operationId":"findPetsByTags","produces":["application/xml","application/
json"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter
by","required":true,"type":"array","items":
{"type":"string"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful
operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":
{"description":"Invalid tag value"}},"security":[{"petstore_auth":
["write:pets","read:pets"]}],"deprecated":true}},"/pet/{petId}":{"get":{"tags":
["pet"],"summary":"Find pet by ID","description":"Returns a single
pet","operationId":"getPetById","produces":["application/xml","application/
json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to
return","required":true,"type":"integer","format":"int64"}],"responses":{"200":
{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":
{"description":"Invalid ID supplied”},…
Service call explained
public interface ServiceCall<Request, Response> {
CompletionStage<Response> invoke(Request request)
}
• ServiceCall is invoked when consuming a service
• JSON is the default serialization format for request/response
messages
• There are two kinds of request/response messages
• Strict
• Streamed
SAM
Strict Messages
default Descriptor descriptor() {
return named("friendservice").withCalls(
namedCall("/api/users", this::createUser)
)
}
ServiceCall<User, String> createUser();
Strict messages are buffered into memory
Streamed Messages
default Descriptor descriptor() {
return named("activityservice").withCalls(
pathCall("/activity/:userId", this::stream)
)
}
ServiceCall<NotUsed, Source<Tweet, ?>> stream(String userId);
• Streamed message is of type Source (Akka streams API)
• Back-pressured, asynchronous handling of messages
• Lagom selects transport protocol (currently WebSockets)
Each service definition is split into two sbt projects: api &
impl
Anatomy Lagom project
my-lagom-system ! Project root

# helloworld-api ! helloworld api project

# helloworld-impl ! helloworld implementation project

# project ! sbt configuration files

# plugins.sbt ! sbt plugins

# build.sbt ! Your project build file
Remember the Service definition?
// this source is placed in your api project
public interface FriendService extends Service {
@Override
default Descriptor descriptor() {
return named("friendservice").withCalls(
namedCall("/api/users", this::createUser)
)
}
ServiceCall<User, String> createUser();
}
Service implementation
import com.lightbend.lagom.javadsl.api.*;
import akka.NotUsed;
import static java.util.concurrent.CompletableFuture.completedFuture;
@Override
public ServiceCall<User, String> createUser() {
return request -> completedFuture("Created user");
}
Exercise
• Implement deleteItem
ex002-startEx001-end
???
Intra-service communication
public class MyModule
extends AbstractModule // Guice
implements ServiceGuiceSupport { // Lagom
protected void configure() {
bindClient(FriendService.class);
…
}
Intra-service communication
public class LikesSrvcImpl implements LikesService {
private final friendService;
@Inject
public LikesSrvcImpl(FriendService friendSrvc) {
this.friendService = friendSrvc;
}
…
Intra-service communication
@Override
public ServiceCall<…,…> like(String id) {
return request -> {
CompletionStage<String> userId =
friendService.createUser(user).invoke()
return userId;
}
}
Circuit breakers
• Circuit breaker is used to provide stability and prevent cascading
failures
• Services calls interacting with Lagom services are using circuit
breakers by default
• Circuit breakers can be configured
• Each service call can have a separate circuit breaker
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Persistence & Clustering
Each service owns
its data
Lagom Persistence
Lagom
Persistence
CQRS Event
Sourcing
implements Using
Commands
Queries
???
= f(data)info
Commands
Queries
???
Data
Info
Events = g(Staten, Command)
Staten+1 = h(Staten, Event)
List<Event> g(State, Command)
State h(State, Event)
(State, Command) -> List<Event>
(State, Event) -> State
State -> Command -> List<Event>
State -> Event -> State
State -> Command -> List<Event>
State -> Event -> State
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
Lagom Workshop  BarcelonaJUG 2017-06-08
State -> Command -> List<Event>
State -> Event -> State
Info = f(data)
Commands
Queries
???
Data
Info
Commands
Queries
Data
Info
Info
Lagom Workshop  BarcelonaJUG 2017-06-08
Data
Info
Info
Journal
Snapshots
Projections
Write-side
Read-side
Exercise
• Implement the InventoryIncreased command handling
ex003-start
Persistent Entities
• Persistent Entity corresponds to an
Aggregate Root (DDD)
• Persistent Entities receive commands
• Triggered by a command, Persistent
Entities will change their state
• Example: CreateBasket, AddItem(“apple”,
3), RemoveItem(“apple”, 7), AddItem(“apple”,
5), Checkout
FriendService
Peter
Bob
Alice
Lagom Cluster
• Lagom allows you to scale
out by forming a cluster of
nodes
• Nodes can be added and
removed dynamically
Node A
Node B
Node C
Node D
join
Lagom Cluster
• Lagom allows you to scale
out by distributing your
Persistent Entities in the
cluster
Node A Node B
Node C
X
Bob
Alice
Z
X
Steve
Paul
Peter
Lagom Cluster
• We have now moved from
a CRUD approach to a
Memory Image approach
• We keep all* our data in
memory!
• See http://
martinfowler.com/bliki/
MemoryImage.html


(*) or a working set, actors can be
passivated and activated as needed
Node A Node B
Node C
X
Bob
Alice
Z
X
Steve
Paul
Peter
Lagom Persistence
• But how does our data
survive a system crash?
• We log all the state
changes!
• Persistent Entities are
recreated on another node
and all state changes are
replayed
Node A Node B
Node C
X
Bob
Alice
Z
X
Y
Paul
Peter
Journal
Snapshots
Write-side
Lagom Persistence
Node A
Node C
X
Bob
Alice
Paul
Peter Steve
Exercise
• Fix the InventoryDecreased command handling and
write a test to prevent bug regressions
ex004-start
Commands
Queries
Data
Info
Info
Event sourcing: Storing deltas
• Every state change is materialized in an Event
• All events are stored in an Event Log
• Current state is constructed by replaying all events
Event Sourcing: Benefits
• Bullet-proof auditing and historical tracing
• Support future ways of looking at data
• No object-relational impedance mismatch
• Performance and scalability
• Testability
• Keep all data in memory
• Optional: Only working set, by using passivation/activation
• Store all state changes as events
• Replay all events to re-create the state
• Optional: Start from snapshot
• Scale out with Lagom Cluster and scalable data store
Event Sourcing with Lagom Persistence
Read-Side
UserCreated(Alice)
FriendAdded(Bob)
FriendAdded(Peter)
FOLLOWERS
userid followedby
Bob Alice
Bob X
Bob Y
Peter Alice
• Derived from event log
• Can be discarded and re-created
• The “book of record” is the event log
• Can be scaled out by creating copies - it’s read only
Read-Side
Consistency
FOLLOWERS
userid followedby
Bob Alice
Bob X
Bob Y
Peter Alice
Data Store
(e.g. Cassandra Cluster)
Alice1 - Persistent
Entity
2 - Journal / Event Log
3 - Read Side
Consistency
Alice
• Persistent Entities define an
Aggregate root
• Aggregate Root is the Transactional
Boundary
• Strong consistency within an
Aggregate Root
• Commands are executed sequentially
on the latest state
• No limit to scalability
1 - Persistent
Entity
Consistency
• Depending on implementation and
configuration
• Popular choice: Casssandra
• “Tunable consistency”
• Use of quorums ensures consistencyData Store
(e.g. Cassandra Cluster)
2 - Journal / Event Log
Consistency
• Will not be updated immediately, but
deferred
• Not much different from queries in
interactive applicationsFOLLOWERS
userid followedby
Bob Alice
Bob X
Bob Y
Peter Alice
3 - Read Side
What if I don’t want to use Event Sourcing?
• Don’t use Lagom Persistence
• You can use any data store
• Beware of blocking APIs (JDBC)
Easter Egg: Broker API
Journal
Snapshots
Write-side
Read-side
Exercise
• Consume the topic where BasketService emits events to
generate the InventoryDecreased commands into
InventoryEntity
ex005-start
Production
• sbt-native packager produces several packages
• ZIP
• Docker
• RPM
• ConductR bundle
Packaging
• Infrastructure need to support Akka clustering
• Deployment tool
• Need to implement Lagom service locator interface
• Should handle node failure scenarios
Production considerations
• Akka Cluster support
• Service locator
• Consolidated logging
• Restart services automatically
• Handling network failures
• Rolling updates
• Monitoring support
ConductR
ConductR
Demo
• Getting started: lightbend.com/lagom
• Examples: lightbend.com/activator/templates
• Contribute: https://github.com/lagom
• Communicate:
• https://groups.google.com/forum/#!forum/lagom-framework
• https://gitter.im/lagom/lagom
• Lightbend Proof of Concept Program: lightbend.com/company/
contact
Try it out
Read this book
https://www.lightbend.com/reactive-
microservices-architecture
(free, registration required)
Lagom Workshop  BarcelonaJUG 2017-06-08

More Related Content

PPTX
Don't Wait! Develop Responsive Applications with Java EE7 Instead
WASdev Community
 
PPTX
Faster Java EE Builds with Gradle
Ryan Cuprak
 
PPTX
Designing Fault Tolerant Microservices
Orkhan Gasimov
 
PPTX
Java EE 8 Update
Ryan Cuprak
 
PPTX
Batching and Java EE (jdk.io)
Ryan Cuprak
 
PDF
20151010 my sq-landjavav2a
Ivan Ma
 
PDF
Short intro to scala and the play framework
Felipe
 
PPTX
Play + scala + reactive mongo
Max Kremer
 
Don't Wait! Develop Responsive Applications with Java EE7 Instead
WASdev Community
 
Faster Java EE Builds with Gradle
Ryan Cuprak
 
Designing Fault Tolerant Microservices
Orkhan Gasimov
 
Java EE 8 Update
Ryan Cuprak
 
Batching and Java EE (jdk.io)
Ryan Cuprak
 
20151010 my sq-landjavav2a
Ivan Ma
 
Short intro to scala and the play framework
Felipe
 
Play + scala + reactive mongo
Max Kremer
 

What's hot (20)

PDF
High-Performance Hibernate - JDK.io 2018
Vlad Mihalcea
 
PDF
Solving the C20K problem: Raising the bar in PHP Performance and Scalability
ZendCon
 
KEY
Multi Client Development with Spring
Joshua Long
 
ZIP
Above the clouds: introducing Akka
nartamonov
 
PDF
Java MySQL Connector & Connection Pool Features & Optimization
Kenny Gryp
 
PPTX
Java EE 8
Ryan Cuprak
 
KEY
Why we chose mongodb for guardian.co.uk
Graham Tackley
 
PPTX
Akka Actor presentation
Gene Chang
 
PPTX
Streamline Hadoop DevOps with Apache Ambari
Jayush Luniya
 
PPTX
Connection Pooling
Wings Interactive
 
PDF
Asynchronous web apps with the Play Framework 2.0
Oscar Renalias
 
PDF
Apache Aries Overview
Ian Robinson
 
PDF
COScheduler
WO Community
 
PDF
JPA and Hibernate Performance Tips
Vlad Mihalcea
 
PDF
Creating Modular Test-Driven SPAs with Spring and AngularJS
Gunnar Hillert
 
PPTX
Concurrency in Scala - the Akka way
Yardena Meymann
 
PPTX
Liberty management
WASdev Community
 
PPTX
SenchaCon 2016: Develop, Test & Deploy with Docker - Jonas Schwabe
Sencha
 
PDF
The Spring Update
Gunnar Hillert
 
PDF
Oozie HUG May12
mislam77
 
High-Performance Hibernate - JDK.io 2018
Vlad Mihalcea
 
Solving the C20K problem: Raising the bar in PHP Performance and Scalability
ZendCon
 
Multi Client Development with Spring
Joshua Long
 
Above the clouds: introducing Akka
nartamonov
 
Java MySQL Connector & Connection Pool Features & Optimization
Kenny Gryp
 
Java EE 8
Ryan Cuprak
 
Why we chose mongodb for guardian.co.uk
Graham Tackley
 
Akka Actor presentation
Gene Chang
 
Streamline Hadoop DevOps with Apache Ambari
Jayush Luniya
 
Connection Pooling
Wings Interactive
 
Asynchronous web apps with the Play Framework 2.0
Oscar Renalias
 
Apache Aries Overview
Ian Robinson
 
COScheduler
WO Community
 
JPA and Hibernate Performance Tips
Vlad Mihalcea
 
Creating Modular Test-Driven SPAs with Spring and AngularJS
Gunnar Hillert
 
Concurrency in Scala - the Akka way
Yardena Meymann
 
Liberty management
WASdev Community
 
SenchaCon 2016: Develop, Test & Deploy with Docker - Jonas Schwabe
Sencha
 
The Spring Update
Gunnar Hillert
 
Oozie HUG May12
mislam77
 
Ad

Similar to Lagom Workshop BarcelonaJUG 2017-06-08 (20)

PDF
Creating Scalable JVM/Java Apps on Heroku
Joe Kutner
 
KEY
Play Support in Cloud Foundry
rajdeep
 
PDF
deep learning in production cff 2017
Ari Kamlani
 
PPTX
Ansible benelux meetup - Amsterdam 27-5-2015
Pavel Chunyayev
 
PPTX
Simplified Cluster Operation and Troubleshooting
DataWorks Summit/Hadoop Summit
 
PPTX
Simplified Cluster Operation & Troubleshooting
DataWorks Summit/Hadoop Summit
 
PPTX
Apache Ambari: Simplified Hadoop Cluster Operation & Troubleshooting
Jayush Luniya
 
PDF
Sprint 17
ManageIQ
 
PDF
Continuous Delivery - Devoxx Morocco 2016
Rafał Leszko
 
PDF
Getting Started with MariaDB with Docker
MariaDB plc
 
PDF
Continuous Delivery - Voxxed Days Thessaloniki 21.10.2016
Rafał Leszko
 
PDF
Scala, ECS, Docker: Delayed Execution @Coursera
C4Media
 
PDF
Advanced technic for OS upgrading in 3 minutes
Hiroshi SHIBATA
 
PDF
Just one-shade-of-openstack
Roberto Polli
 
PDF
Maxim Salnikov - Service Worker: taking the best from the past experience for...
Codemotion
 
PPT
Introduction to Apache CloudStack by David Nalley
buildacloud
 
PPTX
Docker & ECS: Secure Nearline Execution
Brennan Saeta
 
PDF
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
jaxconf
 
PDF
The Play Framework at LinkedIn
Yevgeniy Brikman
 
PPTX
Streamline Hadoop DevOps with Apache Ambari
Alejandro Fernandez
 
Creating Scalable JVM/Java Apps on Heroku
Joe Kutner
 
Play Support in Cloud Foundry
rajdeep
 
deep learning in production cff 2017
Ari Kamlani
 
Ansible benelux meetup - Amsterdam 27-5-2015
Pavel Chunyayev
 
Simplified Cluster Operation and Troubleshooting
DataWorks Summit/Hadoop Summit
 
Simplified Cluster Operation & Troubleshooting
DataWorks Summit/Hadoop Summit
 
Apache Ambari: Simplified Hadoop Cluster Operation & Troubleshooting
Jayush Luniya
 
Sprint 17
ManageIQ
 
Continuous Delivery - Devoxx Morocco 2016
Rafał Leszko
 
Getting Started with MariaDB with Docker
MariaDB plc
 
Continuous Delivery - Voxxed Days Thessaloniki 21.10.2016
Rafał Leszko
 
Scala, ECS, Docker: Delayed Execution @Coursera
C4Media
 
Advanced technic for OS upgrading in 3 minutes
Hiroshi SHIBATA
 
Just one-shade-of-openstack
Roberto Polli
 
Maxim Salnikov - Service Worker: taking the best from the past experience for...
Codemotion
 
Introduction to Apache CloudStack by David Nalley
buildacloud
 
Docker & ECS: Secure Nearline Execution
Brennan Saeta
 
The Play Framework at LinkedIn: productivity and performance at scale - Jim B...
jaxconf
 
The Play Framework at LinkedIn
Yevgeniy Brikman
 
Streamline Hadoop DevOps with Apache Ambari
Alejandro Fernandez
 
Ad

More from Ignasi Marimon-Clos i Sunyol (10)

PDF
The Emperor Has No Docs (Geecon Oct'23)
Ignasi Marimon-Clos i Sunyol
 
PDF
Jeroglificos, Minotauros y la factura de la luz
Ignasi Marimon-Clos i Sunyol
 
PDF
Contributing to Akka (Hacktoberfest 2020)
Ignasi Marimon-Clos i Sunyol
 
PDF
Contributing to OSS (Scalator 2020-01-22)
Ignasi Marimon-Clos i Sunyol
 
PDF
Reactive Microsystems (Sw Crafters Barcelona 2018)
Ignasi Marimon-Clos i Sunyol
 
PDF
Intro scala for rubyists (ironhack)
Ignasi Marimon-Clos i Sunyol
 
PDF
Functional Programming in JAVA 8
Ignasi Marimon-Clos i Sunyol
 
PDF
Scala 101-bcndevcon
Ignasi Marimon-Clos i Sunyol
 
PDF
Spray & Maven Intro for Scala Barcelona Developers Meetup
Ignasi Marimon-Clos i Sunyol
 
The Emperor Has No Docs (Geecon Oct'23)
Ignasi Marimon-Clos i Sunyol
 
Jeroglificos, Minotauros y la factura de la luz
Ignasi Marimon-Clos i Sunyol
 
Contributing to Akka (Hacktoberfest 2020)
Ignasi Marimon-Clos i Sunyol
 
Contributing to OSS (Scalator 2020-01-22)
Ignasi Marimon-Clos i Sunyol
 
Reactive Microsystems (Sw Crafters Barcelona 2018)
Ignasi Marimon-Clos i Sunyol
 
Intro scala for rubyists (ironhack)
Ignasi Marimon-Clos i Sunyol
 
Functional Programming in JAVA 8
Ignasi Marimon-Clos i Sunyol
 
Scala 101-bcndevcon
Ignasi Marimon-Clos i Sunyol
 
Spray & Maven Intro for Scala Barcelona Developers Meetup
Ignasi Marimon-Clos i Sunyol
 

Recently uploaded (20)

PDF
The Future of Mobile Is Context-Aware—Are You Ready?
iProgrammer Solutions Private Limited
 
PDF
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 
PDF
How-Cloud-Computing-Impacts-Businesses-in-2025-and-Beyond.pdf
Artjoker Software Development Company
 
PDF
Oracle AI Vector Search- Getting Started and what's new in 2025- AIOUG Yatra ...
Sandesh Rao
 
PDF
Brief History of Internet - Early Days of Internet
sutharharshit158
 
PPTX
AI and Robotics for Human Well-being.pptx
JAYMIN SUTHAR
 
PDF
Event Presentation Google Cloud Next Extended 2025
minhtrietgect
 
PPTX
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
PDF
The Future of Artificial Intelligence (AI)
Mukul
 
PDF
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
PPTX
IT Runs Better with ThousandEyes AI-driven Assurance
ThousandEyes
 
PDF
Accelerating Oracle Database 23ai Troubleshooting with Oracle AHF Fleet Insig...
Sandesh Rao
 
PDF
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
PDF
Automating ArcGIS Content Discovery with FME: A Real World Use Case
Safe Software
 
PDF
Orbitly Pitch Deck|A Mission-Driven Platform for Side Project Collaboration (...
zz41354899
 
PDF
Trying to figure out MCP by actually building an app from scratch with open s...
Julien SIMON
 
PPTX
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
PDF
The Evolution of KM Roles (Presented at Knowledge Summit Dublin 2025)
Enterprise Knowledge
 
PPTX
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
PDF
Get More from Fiori Automation - What’s New, What Works, and What’s Next.pdf
Precisely
 
The Future of Mobile Is Context-Aware—Are You Ready?
iProgrammer Solutions Private Limited
 
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 
How-Cloud-Computing-Impacts-Businesses-in-2025-and-Beyond.pdf
Artjoker Software Development Company
 
Oracle AI Vector Search- Getting Started and what's new in 2025- AIOUG Yatra ...
Sandesh Rao
 
Brief History of Internet - Early Days of Internet
sutharharshit158
 
AI and Robotics for Human Well-being.pptx
JAYMIN SUTHAR
 
Event Presentation Google Cloud Next Extended 2025
minhtrietgect
 
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
The Future of Artificial Intelligence (AI)
Mukul
 
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
IT Runs Better with ThousandEyes AI-driven Assurance
ThousandEyes
 
Accelerating Oracle Database 23ai Troubleshooting with Oracle AHF Fleet Insig...
Sandesh Rao
 
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
Automating ArcGIS Content Discovery with FME: A Real World Use Case
Safe Software
 
Orbitly Pitch Deck|A Mission-Driven Platform for Side Project Collaboration (...
zz41354899
 
Trying to figure out MCP by actually building an app from scratch with open s...
Julien SIMON
 
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
The Evolution of KM Roles (Presented at Knowledge Summit Dublin 2025)
Enterprise Knowledge
 
What-is-the-World-Wide-Web -- Introduction
tonifi9488
 
Get More from Fiori Automation - What’s New, What Works, and What’s Next.pdf
Precisely
 

Lagom Workshop BarcelonaJUG 2017-06-08

  • 1. Ignasi Marimon-Clos - @ignasi35 (based on slides by Markus Jura - @markusjura) Microservices “Just Right”
  • 8. Lagom - [lah-gome] Adequate, sufficient, just right
  • 9. • Overview • Developer experience • Service API • Persistence & Clustering • Running in production TOC
  • 11. • Reactive • Built-in resiliency & failure handling patterns • Developer experience • No setup of external services • Intra-service communication just works • Services automatically reload on code change • Takes you through to production deployment Why Lagom?
  • 12. • Java 8 • Play 2.5 • Akka 2.4 (Clustering, Streams, Persistence) • Cassandra (default data store) • Jackson (JSON serialization) • Guice (DI) Under the hood
  • 17. • Ocado rip-off • BasketService: CreateBasket, AddItem, RemoveItem • InventoryService: AddItem, ProvisionItem, ReserveItem, PickUpItem • OrderService: PlaceOrder, CompleteOrder, FailOrder • DeliveryService: DeliverOrder Today’s workshop
  • 21. $ sbt runAll $ mvn lagom:runAll
  • 25. Service definition public interface BasketService extends Service { @Override default Descriptor descriptor() { return named("basketservice").withCalls( namedCall("/api/baskets", this::createBasket) ) } ServiceCall<Basket, String> createBasket(); }
  • 26. Service definition public interface BasketService extends Service { @Override default Descriptor descriptor() { return named("basketservice").withCalls( restCall(Method.POST, ”/api/baskets”,this::createBasket) ) } ServiceCall<Basket, String> createBasket(); }
  • 27. Each service definition is split into two sbt projects: api & impl Anatomy Lagom project my-lagom-system ! Project root
 # helloworld-api ! helloworld api project
 # helloworld-impl ! helloworld implementation project
 # project ! sbt configuration files
 # plugins.sbt ! sbt plugins
 # build.sbt ! Your project build file
  • 28. Service definition public interface FriendService extends Service { @Override default Descriptor descriptor() { return named("friendservice").withCalls( restCall(Method.POST, ”/api/users”,this::createUser) ) } ServiceCall<User, String> createUser(); }
  • 29. Service definition public interface FriendService extends Service { @Override default Descriptor descriptor() { return named("friendservice").withCalls( pathCall(”/api/users”,this::createUser) ) } ServiceCall<User, String> createUser(); }
  • 30. Service definition // this source is placed in your api project public interface FriendService extends Service { @Override default Descriptor descriptor() { return named("friendservice").withCalls( namedCall(”createUser", this::createUser) ) } ServiceCall<User, String> createUser(); }
  • 31. Service definition // this source is placed in your api project public interface FriendService extends Service { @Override default Descriptor descriptor() { return named("friendservice").withCalls( call(this::createUser) ) } ServiceCall<User, String> createUser(); }
  • 33. $ sbt runAll $ mvn lagom:runAll
  • 34. $ sbt runAll [info] Loading project definition from /Users/ignasi/workshop/hello/project [info] Set current project to hello-workshop (in build file:/Users/ignasi/workshop/hello/) [info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-kafka... [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Starting Kafka [info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-cassandra... [info] Resolving com.google.guava#guava;18.0 ... SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Starting Cassandra .......... [info] Cassandra server running at 127.0.0.1:4000 [info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-service-locator... [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Service locator is running at http://localhost:8000 [info] Service gateway is running at http://localhost:9000 [info] Compiling 3 Java sources to /Users/ignasi/workshop/hello/hello-api/target/scala-2.11/classes... [info] Compiling 1 Java source to /Users/ignasi/workshop/hello/hello-stream-api/target/scala-2.11/classes... [info] Compiling 4 Java sources to /Users/ignasi/workshop/hello/hello-stream-impl/target/scala-2.11/classes... [info] Compiling 6 Java sources to /Users/ignasi/workshop/hello/hello-impl/target/scala-2.11/classes... [warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 1 : {hello- events=LEADER_NOT_AVAILABLE} [warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 2 : {hello- events=LEADER_NOT_AVAILABLE} [info] Service hello-impl listening for HTTP on 0:0:0:0:0:0:0:0:57797 [info] Service hello-stream-impl listening for HTTP on 0:0:0:0:0:0:0:0:58322 [info] (Services started, press enter to stop and go back to the console...)
  • 36. $ sbt runAll [info] Loading project definition from /Users/ignasi/workshop/hello/project [info] Set current project to hello-workshop (in build file:/Users/ignasi/workshop/hello/) [info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-kafka... [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Starting Kafka [info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-cassandra... [info] Resolving com.google.guava#guava;18.0 ... SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Starting Cassandra .......... [info] Cassandra server running at 127.0.0.1:4000 [info] Updating {file:/Users/ignasi/workshop/hello/}lagom-internal-meta-project-service-locator... [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Service locator is running at http://localhost:8000 [info] Service gateway is running at http://localhost:9000 [info] Compiling 3 Java sources to /Users/ignasi/workshop/hello/hello-api/target/scala-2.11/classes... [info] Compiling 1 Java source to /Users/ignasi/workshop/hello/hello-stream-api/target/scala-2.11/classes... [info] Compiling 4 Java sources to /Users/ignasi/workshop/hello/hello-stream-impl/target/scala-2.11/classes... [info] Compiling 6 Java sources to /Users/ignasi/workshop/hello/hello-impl/target/scala-2.11/classes... [warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 1 : {hello- events=LEADER_NOT_AVAILABLE} [warn] o.a.k.c.NetworkClient - Error while fetching metadata with correlation id 2 : {hello- events=LEADER_NOT_AVAILABLE} [info] Service hello-impl listening for HTTP on 0:0:0:0:0:0:0:0:57797 [info] Service hello-stream-impl listening for HTTP on 0:0:0:0:0:0:0:0:58322 [info] (Services started, press enter to stop and go back to the console...)
  • 38. Service definition // this source is placed in your api project public interface FriendService extends Service { @Override default Descriptor descriptor() { return named("friendservice").withCalls( namedCall(”createUsers", this::createUsers) ) } ServiceCall< Source<User,…> , …> createUsers(); }
  • 40. Exercise • Create service descriptor for Basket Service ex001-start
  • 41. Service definition public interface SearchService extends Service { @Override default Descriptor descriptor() { return named("search").withCalls( pathCall( ”/api/search?pageNo&pageSize“, this::search) ) } ServiceCall<SearchRequest, PageSeq<Result>> search(int pageNo, int pageSize); }
  • 42. … pet/findByStatus”:{“get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","operationId":"findPetsByStatus","produces":["application/xml","application/ json"],"parameters":[{"name":"status","in":"query","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"type":"string","enum": ["available","pending","sold"],"default":"available"},"collectionFormat":"multi"}],"responses ":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/ definitions/Pet"}}},"400":{"description":"Invalid status value"}},"security": [{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags": ["pet"],"summary":"Finds Pets by tags","description":"Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","produces":["application/xml","application/ json"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":true,"type":"array","items": {"type":"string"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400": {"description":"Invalid tag value"}},"security":[{"petstore_auth": ["write:pets","read:pets"]}],"deprecated":true}},"/pet/{petId}":{"get":{"tags": ["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","produces":["application/xml","application/ json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200": {"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400": {"description":"Invalid ID supplied”},…
  • 43. Service call explained public interface ServiceCall<Request, Response> { CompletionStage<Response> invoke(Request request) } • ServiceCall is invoked when consuming a service • JSON is the default serialization format for request/response messages • There are two kinds of request/response messages • Strict • Streamed SAM
  • 44. Strict Messages default Descriptor descriptor() { return named("friendservice").withCalls( namedCall("/api/users", this::createUser) ) } ServiceCall<User, String> createUser(); Strict messages are buffered into memory
  • 45. Streamed Messages default Descriptor descriptor() { return named("activityservice").withCalls( pathCall("/activity/:userId", this::stream) ) } ServiceCall<NotUsed, Source<Tweet, ?>> stream(String userId); • Streamed message is of type Source (Akka streams API) • Back-pressured, asynchronous handling of messages • Lagom selects transport protocol (currently WebSockets)
  • 46. Each service definition is split into two sbt projects: api & impl Anatomy Lagom project my-lagom-system ! Project root
 # helloworld-api ! helloworld api project
 # helloworld-impl ! helloworld implementation project
 # project ! sbt configuration files
 # plugins.sbt ! sbt plugins
 # build.sbt ! Your project build file
  • 47. Remember the Service definition? // this source is placed in your api project public interface FriendService extends Service { @Override default Descriptor descriptor() { return named("friendservice").withCalls( namedCall("/api/users", this::createUser) ) } ServiceCall<User, String> createUser(); }
  • 48. Service implementation import com.lightbend.lagom.javadsl.api.*; import akka.NotUsed; import static java.util.concurrent.CompletableFuture.completedFuture; @Override public ServiceCall<User, String> createUser() { return request -> completedFuture("Created user"); }
  • 50. ???
  • 51. Intra-service communication public class MyModule extends AbstractModule // Guice implements ServiceGuiceSupport { // Lagom protected void configure() { bindClient(FriendService.class); … }
  • 52. Intra-service communication public class LikesSrvcImpl implements LikesService { private final friendService; @Inject public LikesSrvcImpl(FriendService friendSrvc) { this.friendService = friendSrvc; } …
  • 53. Intra-service communication @Override public ServiceCall<…,…> like(String id) { return request -> { CompletionStage<String> userId = friendService.createUser(user).invoke() return userId; } }
  • 54. Circuit breakers • Circuit breaker is used to provide stability and prevent cascading failures • Services calls interacting with Lagom services are using circuit breakers by default • Circuit breakers can be configured • Each service call can have a separate circuit breaker
  • 66. Events = g(Staten, Command) Staten+1 = h(Staten, Event)
  • 68. (State, Command) -> List<Event> (State, Event) -> State
  • 69. State -> Command -> List<Event> State -> Event -> State
  • 70. State -> Command -> List<Event> State -> Event -> State
  • 77. State -> Command -> List<Event> State -> Event -> State Info = f(data)
  • 83. Exercise • Implement the InventoryIncreased command handling ex003-start
  • 84. Persistent Entities • Persistent Entity corresponds to an Aggregate Root (DDD) • Persistent Entities receive commands • Triggered by a command, Persistent Entities will change their state • Example: CreateBasket, AddItem(“apple”, 3), RemoveItem(“apple”, 7), AddItem(“apple”, 5), Checkout FriendService Peter Bob Alice
  • 85. Lagom Cluster • Lagom allows you to scale out by forming a cluster of nodes • Nodes can be added and removed dynamically Node A Node B Node C Node D join
  • 86. Lagom Cluster • Lagom allows you to scale out by distributing your Persistent Entities in the cluster Node A Node B Node C X Bob Alice Z X Steve Paul Peter
  • 87. Lagom Cluster • We have now moved from a CRUD approach to a Memory Image approach • We keep all* our data in memory! • See http:// martinfowler.com/bliki/ MemoryImage.html 
 (*) or a working set, actors can be passivated and activated as needed Node A Node B Node C X Bob Alice Z X Steve Paul Peter
  • 88. Lagom Persistence • But how does our data survive a system crash? • We log all the state changes! • Persistent Entities are recreated on another node and all state changes are replayed Node A Node B Node C X Bob Alice Z X Y Paul Peter
  • 90. Lagom Persistence Node A Node C X Bob Alice Paul Peter Steve
  • 91. Exercise • Fix the InventoryDecreased command handling and write a test to prevent bug regressions ex004-start
  • 93. Event sourcing: Storing deltas • Every state change is materialized in an Event • All events are stored in an Event Log • Current state is constructed by replaying all events
  • 94. Event Sourcing: Benefits • Bullet-proof auditing and historical tracing • Support future ways of looking at data • No object-relational impedance mismatch • Performance and scalability • Testability
  • 95. • Keep all data in memory • Optional: Only working set, by using passivation/activation • Store all state changes as events • Replay all events to re-create the state • Optional: Start from snapshot • Scale out with Lagom Cluster and scalable data store Event Sourcing with Lagom Persistence
  • 97. • Derived from event log • Can be discarded and re-created • The “book of record” is the event log • Can be scaled out by creating copies - it’s read only Read-Side
  • 98. Consistency FOLLOWERS userid followedby Bob Alice Bob X Bob Y Peter Alice Data Store (e.g. Cassandra Cluster) Alice1 - Persistent Entity 2 - Journal / Event Log 3 - Read Side
  • 99. Consistency Alice • Persistent Entities define an Aggregate root • Aggregate Root is the Transactional Boundary • Strong consistency within an Aggregate Root • Commands are executed sequentially on the latest state • No limit to scalability 1 - Persistent Entity
  • 100. Consistency • Depending on implementation and configuration • Popular choice: Casssandra • “Tunable consistency” • Use of quorums ensures consistencyData Store (e.g. Cassandra Cluster) 2 - Journal / Event Log
  • 101. Consistency • Will not be updated immediately, but deferred • Not much different from queries in interactive applicationsFOLLOWERS userid followedby Bob Alice Bob X Bob Y Peter Alice 3 - Read Side
  • 102. What if I don’t want to use Event Sourcing? • Don’t use Lagom Persistence • You can use any data store • Beware of blocking APIs (JDBC)
  • 105. Exercise • Consume the topic where BasketService emits events to generate the InventoryDecreased commands into InventoryEntity ex005-start
  • 107. • sbt-native packager produces several packages • ZIP • Docker • RPM • ConductR bundle Packaging
  • 108. • Infrastructure need to support Akka clustering • Deployment tool • Need to implement Lagom service locator interface • Should handle node failure scenarios Production considerations
  • 109. • Akka Cluster support • Service locator • Consolidated logging • Restart services automatically • Handling network failures • Rolling updates • Monitoring support ConductR
  • 111. • Getting started: lightbend.com/lagom • Examples: lightbend.com/activator/templates • Contribute: https://github.com/lagom • Communicate: • https://groups.google.com/forum/#!forum/lagom-framework • https://gitter.im/lagom/lagom • Lightbend Proof of Concept Program: lightbend.com/company/ contact Try it out