65
@crichardson Building microservices with Scala, functional domain models and Spring Boot Chris Richardson Author of POJOs in Action Founder of the original CloudFoundry.com @crichardson [email protected] http://plainoldobjects.com

#JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

Embed Size (px)

DESCRIPTION

In this talk you will learn about a modern way of designing applications that’s very different from the traditional approach of building monolithic applications that persist mutable domain objects in a relational database.We will talk about the microservice architecture, it’s benefits and drawbacks and how Spring Boot can help. You will learn about implementing business logic using functional, immutable domain models written in Scala. We will describe event sourcing and how it’s an extremely useful persistence mechanism for persisting functional domain objects in a microservices architecture.

Citation preview

Page 1: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Building microservices with Scala, functional domain models and Spring Boot

Chris Richardson

Author of POJOs in ActionFounder of the original CloudFoundry.com

@[email protected]://plainoldobjects.com

Page 2: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Presentation goal

Share my experiences with building an application using Scala, functional domain

models, microservices, event sourcing, CQRS, and Spring Boot

Page 3: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

About Chris

Page 4: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

About Chris

Founder of a buzzword compliant (stealthy, social, mobile, big data, machine learning, ...) startup

Consultant helping organizations improve how they architect and deploy applications using cloud, micro services, polyglot applications, NoSQL, ...

Page 5: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Agenda

Why build event-driven microservices?

Overview of event sourcing

Designing microservices with event sourcing

Implementing queries in an event sourced application

Building and deploying microservices

Page 6: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Let’s imagine that you are building a banking app...

Page 7: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Domain model

Account

balance

open(initialBalance)debit(amount)credit(amount)

MoneyTransfer

fromAccountIdtoAccountIdamount

Page 8: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Tomcat

Traditional application architecture

Browser/Client

WAR/EAR

RDBMS

Customers

Accounts

Transfers

Banking Banking UI

developtest

deploy

Simple to

Load balancer

scale

Spring MVC

SpringHibernate

...

HTMLREST/JSON

Page 9: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Problem #1: monolithic architecture

Intimidates developers

Obstacle to frequent deployments

Overloads your IDE and container

Obstacle to scaling development

Requires long-term commitment to a technology stack

Page 10: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Solution #1: use a microservice architecture

Banking UI

Account Management Service

Transaction Management Service

Account Database

Transaction Database

Standaloneservices

Page 11: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Problem #2: relational databases

Scalability

Distribution

Schema updates

O/R impedance mismatch

Handling semi-structured data

Page 12: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Solution #2: use NoSQL databases

Avoids the limitations of RDBMS

For example,

text search ⇒ Solr/Cloud Search

social (graph) data ⇒ Neo4J

highly distributed/available database ⇒ Cassandra

...

Page 13: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Different modules use different databases

IEEE Software Sept/October 2010 - Debasish Ghosh / Twitter @debasishg

Page 14: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

But now we have problems with data consistency!

Page 15: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Problem #3: Microservices = distributed data management

Each microservice has it’s own database

Some data is replicated and must be kept in sync

Business transactions must update data owned by multiple services

Tricky to implement reliably without 2PC

Page 16: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Problem #4: NoSQL = ACID-free, denormalized databases

Limited transactions, i.e. no ACID transactions

Tricky to implement business transactions that update multiple rows, e.g. http://bit.ly/mongo2pc

Limited querying capabilities

Requires denormalized/materialized views that must be synchronized

Multiple datastores (e.g. DynamoDB + Cloud Search ) that need to be kept in sync

Page 17: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Solution to #3/#4: Event-based architecture to the rescue

Microservices publish events when state changes

Microservices subscribe to events

Synchronize replicated data

Maintains eventual consistency across multiple aggregates (in multiple datastores)

Page 18: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Eventually consistent money transfer

Message Bus

MoneyTransferService AccountService

transferMoney()

Publishes:Subscribes to:

Subscribes to:

publishes:MoneyTransferCreatedEvent

AccountDebitedEvent

DebitRecordedEvent

AccountCreditedEventMoneyTransferCreatedEvent

DebitRecordedEvent

AccountDebitedEventAccountCreditedEvent

Page 19: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

To maintain consistency a service must

atomically publish an event whenever

a domain object changes

Page 20: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

How to generate events reliably?

Database triggers, Hibernate event listener, ...

Reliable BUT

Not with NoSQL

Disconnected from the business level event

Limited applicability

Ad hoc event publishing code mixed into business logic

Messy code, poor separation of concerns

Unreliable, e.g. too easy to forget to publish an event

Page 21: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

How to atomically update datastore and publish event(s)

Use 2PC

Database and message broker must support 2PC

2PC is best avoided

Use datastore as a message queue

1. Update database: new entity state & event to publish

2. Publish event & mark event as published

Eventually consistent mechanism

See BASE: An Acid Alternative, http://bit.ly/ebaybase

• Difficult to implement when using a NoSQL database :-(

Page 22: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Agenda

Why build event-driven microservices?

Overview of event sourcing

Designing microservices with event sourcing

Implementing queries in an event sourced application

Building and deploying microservices

Page 23: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Event sourcingFor each aggregate:

Identify (state-changing) domain events

Define Event classes

For example,

Account: AccountOpenedEvent, AccountDebitedEvent, AccountCreditedEvent

ShoppingCart: ItemAddedEvent, ItemRemovedEvent, OrderPlacedEvent

Page 24: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Persists events NOT current state

Account

balance

open(initial)debit(amount)credit(amount)

AccountOpened

Event table

AccountCredited

AccountDebited

101 450

Account tableX101

101

101

901

902

903

500

250

300

Page 25: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Replay events to recreate state

Account

balance

AccountOpenedEvent(balance)AccountDebitedEvent(amount)AccountCreditedEvent(amount)

Events

Page 26: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Aggregate traits

Map Command to Events

Apply event returning updated Aggregate

Page 27: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Account - command processing

Prevent overdraft

Page 28: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Account - applying eventsImmutable

Page 29: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Request handling in an event-sourced application

HTTPHandler

EventStore

pastEvents = findEvents(entityId)

Account

new()

applyEvents(pastEvents)

newEvents = processCmd(SomeCmd)

saveEvents(newEvents)

Microservice A

Page 30: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Event Store publishes events - consumed by other services

EventStore

EventSubscriber

subscribe(EventTypes)

publish(event)

publish(event)

Aggregate

NoSQLmaterialized

view

update()

update()

Microservice B

Page 31: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Persisting events

Ideally use a cross platform format

Use weak serialization:

enables event evolution, eg. add memo field to transfer

missing field ⇒ provide default value

unknown field ⇒ ignore

JSON is a good choice

Page 32: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Optimizing using snapshots

Most aggregates have relatively few events

BUT consider a 10-year old Account ⇒ many transactions

Therefore, use snapshots:

Periodically save snapshot of aggregate state

Typically serialize memento of the aggregate

Load latest snapshot + subsequent events

Page 33: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Event Store APItrait EventStore {

def save[T <: Aggregate[T]](entity: T, events: Seq[Event], assignedId : Option[EntityId] = None): Future[EntityWithIdAndVersion[T]]

def update[T <: Aggregate[T]](entityIdAndVersion : EntityIdAndVersion, entity: T, events: Seq[Event]): Future[EntityWithIdAndVersion[T]]

def find[T <: Aggregate[T] : ClassTag](entityId: EntityId) : Future[EntityWithIdAndVersion[T]]

def findOptional[T <: Aggregate[T] : ClassTag](entityId: EntityId) Future[Option[EntityWithIdAndVersion[T]]]

def subscribe(subscriptionId: SubscriptionId): Future[AcknowledgableEventStream]}

Page 34: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Business benefits of event sourcing

Built-in, reliable audit log

Enables temporal queries

Publishes events needed by big data/predictive analytics etc.

Preserved history ⇒ More easily implement future requirements

Page 35: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Technical benefits of event sourcing

Solves data consistency issues in a Microservice/NoSQL-based architecture:

Atomically save and publish events

Event subscribers update other aggregates ensuring eventual consistency

Event subscribers update materialized views in SQL and NoSQL databases (more on that later)

Eliminates O/R mapping problem

Page 36: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Drawbacks of event sourcing

Weird and unfamiliar

Events = a historical record of your bad design decisions

Handling duplicate events can be tricky

Application must handle eventually consistent data

Event store only directly supports PK-based lookup (more on that later)

Page 37: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Example of an eventual consistency problem

Scenario:

1.Create the user

2.Create shopping cart

3.Update the user with the shopping cart’s id

The user temporarily does not have a shopping cart id!

Client might need to retry their request at a later point

Server should return status code 418??

Page 38: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Handling duplicate messages

Idempotent operations

e.g. add item to shopping cart

Duplicate detection:

e.g. track most recently seen event and discard earlier ones

Page 39: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Agenda

Why build event-driven microservices?

Overview of event sourcing

Designing microservices with event sourcing

Implementing queries in an event sourced application

Building and deploying microservices

Page 40: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

The anatomy of a microservice

Event Store

HTTP Request

HTTP Adapter

Aggregate

Event Adapter

Cmd

Cmd

EventsEvents

Xyz Adapter

Xyz Request

microservice

Page 41: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Asynchronous Spring MVC controller

Page 42: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

MoneyTransferService

DSL concisely specifies:1.Creates MoneyTransfer aggregate2.Processes command3.Applies events4.Persists events

Page 43: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

MoneyTransfer Aggregate

Page 44: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Handling events published by Accounts

1.Load MoneyTransfer aggregate2.Processes command3.Applies events4.Persists events

Page 45: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Agenda

Why build event-driven microservices?

Overview of event sourcing

Designing microservices with event sourcing

Implementing queries in an event sourced application

Building and deploying microservices

Page 46: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Let’s imagine that you want to display an account and it’s recent transactions...

Page 47: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Displaying balance + recent credits and debits

We need to do a “join: between the Account and the corresponding MoneyTransfers

(Assuming Debit/Credit events don’t include other account, ...)

BUTEvent Store = primary key lookup of individual aggregates, ...

⇒Use Command Query Responsibility Separation

Define separate “materialized” query-side views that implement those queries

Page 48: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Query-side microservices

Event Store

Updater - microservice

View UpdaterService

EventsReader - microservice

HTTP GET Request

View Query Service

ViewStore

e.g. MongoDB

Neo4JCloudSearch

update query

Page 49: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Persisting account balance and recent transactions in MongoDB

{ id: "298993498", balance: 100000, transfers : [

{"transferId" : "4552840948484", "fromAccountId" : 298993498, "toAccountId" : 3483948934, "amount" : 5000}, ...

], changes: [ {"changeId" : "93843948934", "transferId" : "4552840948484", "transactionType" : "AccountDebited", "amount" : 5000}, ... ] }

Denormalized = efficient lookup

Transfers that update the account

The sequence of debits and credits

Page 50: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Updating MongoDB using Spring Data

class AccountInfoUpdateService (mongoTemplate : MongoTemplate, ...) extends CompoundEventHandler {

@EventHandler def recordDebit(de: DispatchedEvent[AccountDebitedEvent]) = { ... val ci = AccountChangeInfo(...)

mongoTemplate.updateMulti( new Query(where("id").is(de.entityId.id).and("version").lt(changeId)), new Update(). dec("balance", amount). push("changes", ci). set("version", changeId), classOf[AccountInfo]) }

@EventHandler def recordTransfer(de: DispatchedEvent[MoneyTransferCreatedEvent]) = ...

}

insert/In-place update

duplicate event detection

updates to and from accounts

Page 51: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Retrieving account info from MongoDB using Spring Data

class AccountInfoQueryService(accountInfoRepository : AccountInfoRepository) {

def findByAccountId(accountId : EntityId) : AccountInfo = accountInfoRepository.findOne(accountId.id)

}

case class AccountInfo(id : String, balance : Long, transactions : List[AccountTransactionInfo], changes : List[ChangeInfo], version : String)

case class AccountTransactionInfo(changeId : String, transactionId : String,

transactionType : String, amount : Long, balanceDelta : Long)

trait AccountInfoRepository extends MongoRepository[AccountInfo, String]

Implementation generated by Spring Data

Page 52: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Other kinds of viewsAWS Cloud Search

Text search as-a-Service

View updater batches aggregates to index

View query does text search

AWS DynamoDB

NoSQL as-a-Service

On-demand scalable - specify desired read/write capacity

Document and key-value data models

Useful for denormalized, UI oriented views

Page 53: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Dealing with eventually consistent views

Scenario:

Client creates/updates aggregate

Client requests view of aggregate

But the view might not yet have been updated

Solution:

Create/Update response contains aggregate version

Query request contains desired version

Alternatively:

“Fake it” in the UI until the view is updated

Page 54: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Agenda

Why build event-driven microservices?

Overview of event sourcing

Designing microservices with event sourcing

Implementing queries in an event sourced application

Building and deploying microservices

Page 55: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Building microservices with Spring Boot

Makes it easy to create stand-alone, production ready Spring Applications

Automatically configures Spring using Convention over Configuration

Externalizes configuration

Generates standalone executable JARs with embedded web server

Page 56: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Spring Boot simplifies configuration

Spring Container

Application components

Fully configured application

ConfigurationMetadata

•Typesafe JavaConfig•Annotations•Legacy XML

Default Configuration

Metadata

Spring BootYou write less

of this

Inferred from CLASSPATH

Page 57: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Tiny Spring configuration for Account microservice

@Configuration@EnableAutoConfiguration@Import(classOf[JdbcEventStoreConfiguration]))@ComponentScanclass AccountConfiguration {

@Bean def accountService(eventStore : EventStore) = new AccountService(eventStore)

@Bean def accountEventHandlers(eventStore : EventStore) = EventHandlerRegistrar.makeFromCompoundEventHandler( eventStore, "accountEventHandlers", new TransferWorkflowAccountHandlers(eventStore)) @Bean @Primary def scalaObjectMapper() = ScalaObjectMapper

}

Service

Event handlers

Scan for controllers

Customize JSON serialization

Page 58: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

The Main program

object BankingMain extends App {

SpringApplication.run(classOf[AccountConfiguration], args :_ *)

}

Page 59: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Building with Gradle

buildscript { repositories { mavenCentral() } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.0.RELEASE") }}

apply plugin: 'scala'apply plugin: 'spring-boot'

...

Page 60: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Running the microservice

$ java -jar build/libs/banking-main-1.0-SNAPSHOT.jar --server.port=8081...11:38:04.633 INFO n.c.e.e.bank.web.main.BankingMain$ - Started BankingMain. in 8.811 seconds (JVM running for 9.884)

$ curl localhost:8081/health{"status":"UP", "mongo":{"status":"UP","version":"2.4.10"}, "db":{"status":"UP","database":"H2","hello":1}}

Built in health checks

Command line arg processing

Page 61: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

NodeJS-based API gateway

Motivations

Small footprint

Efficient, event-driven I/O

Availability of modules: express, request, googleapis, ...

Routes requests to the appropriate backend service based on URL prefix

Handles Google/OAuth-based authentication

Page 62: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Jenkins-based deployment pipeline

Build & Testmicro-service

Build & TestDockerimage

Deploy Docker image

to registry

One pipeline per micro-service

Page 63: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Production environment

EC2 instance running Docker

Deployment tool:

1.Compares running containers with what’s been built by Jenkins

2.Pulls latest images from Docker registry

3.Stops old versions

4.Launches new versions

Page 64: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Summary

Event Sourcing solves key data consistency issues with:

Microservices

Partitioned/NoSQL databases

Spring and Scala play nicely together

Spring Boot makes it very easily to build production ready microservices

NodeJS is useful for building network-centric services

Page 65: #JaxLondon: Building microservices with Scala, functional domain models and Spring Boot

@crichardson

Questions?

@crichardson [email protected]

http://plainoldobjects.com http://microservices.io