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
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();
}
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();
}
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
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();
}
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;
}
…
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
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
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
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)
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