Ignasi Marimon-Clos - @ignasi35
(based on slides by Markus Jura - @markusjura)
Microservices “Just Right”
Disclaimer
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
• 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
Enough slides,
Demo time
$ sbt runAll
$ mvn lagom:runAll
Service API
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();
}
$ 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
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
State -> Command -> List<Event>
State -> Event -> State
Info = f(data)
Commands
Queries
???
Data
Info
Commands
Queries
Data
Info
Info
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

Lagom Workshop BarcelonaJUG 2017-06-08

  • 1.
    Ignasi Marimon-Clos -@ignasi35 (based on slides by Markus Jura - @markusjura) Microservices “Just Right”
  • 3.
  • 8.
    Lagom - [lah-gome] Adequate,sufficient, just right
  • 9.
    • Overview • Developerexperience • Service API • Persistence & Clustering • Running in production TOC
  • 10.
  • 11.
    • Reactive • Built-inresiliency & 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
  • 13.
  • 17.
    • Ocado rip-off •BasketService: CreateBasket, AddItem, RemoveItem • InventoryService: AddItem, ProvisionItem, ReserveItem, PickUpItem • OrderService: PlaceOrder, CompleteOrder, FailOrder • DeliveryService: DeliverOrder Today’s workshop
  • 18.
  • 20.
  • 21.
    $ sbt runAll $mvn lagom:runAll
  • 22.
  • 25.
    Service definition public interfaceBasketService extends Service { @Override default Descriptor descriptor() { return named("basketservice").withCalls( namedCall("/api/baskets", this::createBasket) ) } ServiceCall<Basket, String> createBasket(); }
  • 26.
    Service definition public interfaceBasketService extends Service { @Override default Descriptor descriptor() { return named("basketservice").withCalls( restCall(Method.POST, ”/api/baskets”,this::createBasket) ) } ServiceCall<Basket, String> createBasket(); }
  • 27.
    Each service definitionis 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 interfaceFriendService 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 interfaceFriendService extends Service { @Override default Descriptor descriptor() { return named("friendservice").withCalls( pathCall(”/api/users”,this::createUser) ) } ServiceCall<User, String> createUser(); }
  • 30.
    Service definition // thissource 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 // thissource 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...)
  • 35.
  • 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...)
  • 37.
  • 38.
    Service definition // thissource 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(); }
  • 39.
  • 40.
    Exercise • Create servicedescriptor for Basket Service ex001-start
  • 41.
    Service definition public interfaceSearchService 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 Petsby 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 publicinterface 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 Descriptordescriptor() { return named("friendservice").withCalls( namedCall("/api/users", this::createUser) ) } ServiceCall<User, String> createUser(); Strict messages are buffered into memory
  • 45.
    Streamed Messages default Descriptordescriptor() { 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 definitionis 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 Servicedefinition? // 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.*; importakka.NotUsed; import static java.util.concurrent.CompletableFuture.completedFuture; @Override public ServiceCall<User, String> createUser() { return request -> completedFuture("Created user"); }
  • 49.
  • 50.
  • 51.
    Intra-service communication public classMyModule extends AbstractModule // Guice implements ServiceGuiceSupport { // Lagom protected void configure() { bindClient(FriendService.class); … }
  • 52.
    Intra-service communication public classLikesSrvcImpl 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 • Circuitbreaker 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
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
    Events = g(Staten,Command) Staten+1 = h(Staten, Event)
  • 67.
  • 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)
  • 78.
  • 79.
  • 81.
  • 82.
  • 83.
    Exercise • Implement theInventoryIncreased command handling ex003-start
  • 84.
    Persistent Entities • PersistentEntity 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 • Lagomallows 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 • Lagomallows 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 • Wehave 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 • Buthow 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
  • 89.
  • 90.
    Lagom Persistence Node A NodeC X Bob Alice Paul Peter Steve
  • 91.
    Exercise • Fix theInventoryDecreased command handling and write a test to prevent bug regressions ex004-start
  • 92.
  • 93.
    Event sourcing: Storingdeltas • 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 alldata 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
  • 96.
  • 97.
    • Derived fromevent 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 BobX Bob Y Peter Alice Data Store (e.g. Cassandra Cluster) Alice1 - Persistent Entity 2 - Journal / Event Log 3 - Read Side
  • 99.
    Consistency Alice • Persistent Entitiesdefine 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 onimplementation and configuration • Popular choice: Casssandra • “Tunable consistency” • Use of quorums ensures consistencyData Store (e.g. Cassandra Cluster) 2 - Journal / Event Log
  • 101.
    Consistency • Will notbe 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 Idon’t want to use Event Sourcing? • Don’t use Lagom Persistence • You can use any data store • Beware of blocking APIs (JDBC)
  • 103.
  • 104.
  • 105.
    Exercise • Consume thetopic where BasketService emits events to generate the InventoryDecreased commands into InventoryEntity ex005-start
  • 106.
  • 107.
    • sbt-native packagerproduces several packages • ZIP • Docker • RPM • ConductR bundle Packaging
  • 108.
    • Infrastructure needto support Akka clustering • Deployment tool • Need to implement Lagom service locator interface • Should handle node failure scenarios Production considerations
  • 109.
    • Akka Clustersupport • Service locator • Consolidated logging • Restart services automatically • Handling network failures • Rolling updates • Monitoring support ConductR
  • 110.
  • 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
  • 112.