SlideShare une entreprise Scribd logo
1  sur  119
Télécharger pour lire hors ligne
Beyond REST and CRUD: CQRS/ES mit GraphQL
François Fernandes
Senior Solution Architect
francois.fernandes@digitalfrontiers.de
@tellme_francois
github.com/fernanfs
?
2
Was stimmt nicht mit
REST und CRUD
3
Was ist denn eigentlich REST?
• Alle relevanten Daten sind über eine URI erreichbar
http://smart.service/api/resources/<resource-id>
• Änderungen an den Daten werden mittels HTTP Requests und
passenden Verben durchgeführt
POST http://smart.service/api/resources/
GET http://smart.service/api/resources/<resource-id>
DELETE http://smart.service/api/resources/<resource-id>
3
Was ist denn eigentlich REST?
• Alle relevanten Daten sind über eine URI erreichbar
http://smart.service/api/resources/<resource-id>
• Änderungen an den Daten werden mittels HTTP Requests und
passenden Verben durchgeführt
POST http://smart.service/api/resources/
GET http://smart.service/api/resources/<resource-id>
DELETE http://smart.service/api/resources/<resource-id>
Kein REST ist:
GET http://dumb.service/api/resources/<id>/applyColorBlue
4
Betrachten wir ein Beispiel
Verwaltung für Gutscheinkarten
4
Betrachten wir ein Beispiel
Verwaltung für Gutscheinkarten
• Ausstellen neuer Karten
4
Betrachten wir ein Beispiel
Verwaltung für Gutscheinkarten
• Ausstellen neuer Karten
• Guthaben einlösen
4
Betrachten wir ein Beispiel
Verwaltung für Gutscheinkarten
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
4
Betrachten wir ein Beispiel
Verwaltung für Gutscheinkarten
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
4
Betrachten wir ein Beispiel
Verwaltung für Gutscheinkarten
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
5
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
6
Ausstellen neuer Karten
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
POST http://svc.local/api/giftcards
Accept: application/json
Content-Type: application/json
{
"amount": 42
}
Request HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 102,
"amount": 42
}
Response
7
Guthaben einlösen
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
POST http://svc.local/api/giftcards/102/transactions
Accept: application/json
Content-Type: application/json
{
"amount": 11
}
Request HTTP/1.1 201 CREATED
Content-Type: application/json
{
"id": 203,
"amount": -11,
"description": "Redeem"
}
Response
8
Guthaben erhöhen
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
PUT http://svc.local/api/giftcards/102
Accept: application/json
Content-Type: application/json
{
"amount": 80
}
Request HTTP/1.1 201 CREATED
Content-Type: application/json
{
"id": 102,
"amount": 80
}
Response
8
Guthaben erhöhen
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
PUT http://svc.local/api/giftcards/102
Accept: application/json
Content-Type: application/json
{
"amount": 80
}
Request HTTP/1.1 201 CREATED
Content-Type: application/json
{
"id": 102,
"amount": 80
}
Response
Ist das der Betrag um
den das Guthaben
erhöht werden soll?
Oder ist es der neue
Zielbetrag?
9
Karte stornieren
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
Was ist eigentlich eine Stornierung?
• Löschung mittels DELETE?
Der Datensatz sollte weiterhin existieren für
eine Nachverfolgung
• Eine Aktualisierung mittels PUT?
Das steht in Konkurrenz zu Erhöhung des
Guthabens
10
Karte stornieren
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
PUT http://svc.local/api/giftcards/102
Accept: application/json
Content-Type: application/json
{
"revoked": true
}
Request HTTP/1.1 200
Content-Type: application/json
{
"id": 102,
"amount": 80,
"revoked": true
}
Response
11
Überlappung
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
@PutMapping("{id}")
fun updateGiftCard(
@PathVariable id: Long,
@RequestBody input: GiftCardUpdateInput): GiftCardDto {
return if (input.amount != null)
service.increaseAmountTo(id, input.amount).asDto()
else if (input.revoked == true)
service.revokeCard(id).asDto()
else
// intention unclear
throw UnknownGiftCardUpdateRequestException()
}
12
Einlösung stornieren
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
Auf welcher Ressource (URI) operieren wir?
• DELETE http://svc.local/api/giftcards/102/transactions/203
Löschung mittels DELETE
• POST http://svc.local/api/giftcards/102/transactions
Erstellen eines Storno-Datensatzes
13
Einlösung stornieren
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
DELETE http://svc.local/api/giftcards/102/transactions/203
• Der Datensatz soll nicht gelöscht werden
• Auch wird der Datensatz selbst nicht geändert, sondern ein
neuer angelegt
14
Einlösung stornieren
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
POST http://svc.local/api/giftcards/102/transactions
• Der Body des POST könnte ein Flag tragen, das den Datensatz
als Stornierung kennzeichnet
• Steht in Konkurrenz zu normalen Transaktionen
POST http://svc.local/api/giftcards/102/transactions
Accept: application/json
Content-Type: application/json
{
"amount": -11,
„revokedTransactionId": 203
}
Request
15
Schnelleinstieg
GraphQL
16
GraphQL Schnelleinstieg
• GraphQL ist ein Mechanismus für die Kommunikation
zwischen Client und Server
• Die technische Übertragung ist typischerweise HTTP
• Kern von GraphQL ist das GraphQL Schema
• Das GraphQL Schema definiert, welche Daten der Server
bereitstellen kann
17
GraphQL Schnelleinstieg: Das Schema
• Das GraphQL Schema basiert auf
Typen
type Type {
field: StrictType
}
type StrictType {
valueNeverNull: String!
}
type PrimitiveFields {
identifier: ID!
numeric: Int!
}
type TypeWithArrays {
strings: [String]
strictList: [StrictType!]!
}
18
GraphQL Schnelleinstieg: Das Schema
• Das GraphQL Schema basiert auf
Typen
• Ein Typ besteht aus Feldern und
deren Typisierung
type Type {
field: StrictType
}
type StrictType {
valueNeverNull: String!
}
type PrimitiveFields {
identifier: ID!
numeric: Int!
}
type TypeWithArrays {
strings: [String]
strictList: [StrictType!]!
}
19
GraphQL Schnelleinstieg: Das Schema
• Das GraphQL Schema basiert auf
Typen
• Ein Typ besteht aus Feldern und
deren Typisierung
• Felder können explizit als non-
nullable deklariert werden
type Type {
field: StrictType
}
type StrictType {
valueNeverNull: String!
}
type PrimitiveFields {
identifier: ID!
numeric: Int!
}
type TypeWithArrays {
strings: [String]
strictList: [StrictType!]!
}
20
GraphQL Schnelleinstieg: Das Schema
• Das GraphQL Schema basiert auf
Typen
• Ein Typ besteht aus Feldern und
deren Typisierung
• Felder können explizit als non-
nullable deklariert werden
• Die Typen der Felder sind entweder
deklarierte Typen oder Scalars
type Type {
field: StrictType
}
type StrictType {
valueNeverNull: String!
}
type PrimitiveFields {
identifier: ID!
numeric: Int!
}
type TypeWithArrays {
strings: [String]
strictList: [StrictType!]!
}
21
GraphQL Schnelleinstieg: Das Schema
• Das GraphQL Schema basiert auf
Typen
• Ein Typ besteht aus Feldern und
deren Typisierung
• Felder können explizit als non-
nullable deklariert werden
• Die Typen der Felder sind entweder
deklarierte Typen oder Scalars
• Listen werden ebenfalls typisiert
definiert
type Type {
field: StrictType
}
type StrictType {
valueNeverNull: String!
}
type PrimitiveFields {
identifier: ID!
numeric: Int!
}
type TypeWithArrays {
strings: [String]
strictList: [StrictType!]!
}
22
GraphQL Schnelleinstieg: Das Schema
• Felder können Argumente erhalten
• Anhand der Argumente kann der
Zugriff auf das Feld konfiguriert
werden
type Type {
field(filter: String!): ResultType
other(filter: Filter!): ResultType
}
input Filter {
text: String
maxResults: Int
}
23
GraphQL Schnelleinstieg: Das Schema
• Felder können Argumente erhalten
• Anhand der Argumente kann der
Zugriff auf das Feld konfiguriert
werden
• Komplexere Argumente können in
eigenen Input-Typen definiert
werden
type Type {
field(filter: String!): ResultType
other(filter: Filter!): ResultType
}
input Filter {
text: String
maxResults: Int
}
24
GraphQL Schnelleinstieg: Das Schema
Das GraphQL Schema bietet noch weitere Möglichkeiten
• Union Types
• Enum
• Eigene Skalare
• Interfaces
25
GraphQL Schnelleinstieg: Das Schema
Das Schema definiert drei
Einstiegspunkte in den Graphen:
26
GraphQL Schnelleinstieg: Das Schema
Das Schema definiert drei
Einstiegspunkte in den Graphen:
• Query
type Query {
#...
}
27
GraphQL Schnelleinstieg: Das Schema
Das Schema definiert drei
Einstiegspunkte in den Graphen:
• Query
• Mutation
type Query {
#...
}
type Mutation {
#...
}
28
GraphQL Schnelleinstieg: Das Schema
Das Schema definiert drei
Einstiegspunkte in den Graphen:
• Query
• Mutation
• Subscription
type Query {
#...
}
type Mutation {
#...
}
type Subscription {
#...
}
29
GraphQL Schnelleinstieg: Das Schema
• Query ist der Einstieg in den
Graphen
• Daten die für den Client abrufbar
sein sollen, müssen direkt oder
indirekt über Query erreichbar sein
• Die Komplexität des Graphen ist
wenig limitiert
type Query {
companies: [Company!]!
company(id: ID!): Company
}
type Company {
id: ID!
name: String!
locations: [Location!]!
}
type Location {
name: String!
address: Address
}
type Address {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
30
GraphQL Schnelleinstieg
Queries
31
GraphQL Schnelleinstieg
Queries
• Abfragen in dem Graphen werden mittels Queries definiert
• Die Query-Sprache ist stark an dem GraphQL Schema
angelehnt
32
GraphQL Schnelleinstieg: Queries
Eine Query definiert, welche Daten aus dem Grafen
ausgelesen werden sollen
type Query {
companies: [Company!]!
company(id: ID!): Company
}
type Company {
id: ID!
name: String!
locations: [Location!]!
}
type Location {
name: String!
address: Address
}
type Address {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
32
GraphQL Schnelleinstieg: Queries
Eine Query definiert, welche Daten aus dem Grafen
ausgelesen werden sollen
type Query {
companies: [Company!]!
company(id: ID!): Company
}
type Company {
id: ID!
name: String!
locations: [Location!]!
}
type Location {
name: String!
address: Address
}
type Address {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
query {
companies {
name
locations {
name
}
}
}
32
GraphQL Schnelleinstieg: Queries
Eine Query definiert, welche Daten aus dem Grafen
ausgelesen werden sollen
type Query {
companies: [Company!]!
company(id: ID!): Company
}
type Company {
id: ID!
name: String!
locations: [Location!]!
}
type Location {
name: String!
address: Address
}
type Address {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
query {
companies {
name
locations {
name
}
}
}
{
"data": {
"companies": [
{
"name": "Apple",
"locations": [
{
"name": "Campus"
}
]
}
]
}
}
32
GraphQL Schnelleinstieg: Queries
Eine Query definiert, welche Daten aus dem Grafen
ausgelesen werden sollen
type Query {
companies: [Company!]!
company(id: ID!): Company
}
type Company {
id: ID!
name: String!
locations: [Location!]!
}
type Location {
name: String!
address: Address
}
type Address {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
query {
companies {
name
locations {
name
}
}
}
{
"data": {
"companies": [
{
"name": "Apple",
"locations": [
{
"name": "Campus"
}
]
}
]
}
}
Ein GraphQL Server antwortet nur mit den
Daten, die explizit angefordert wurden
33
GraphQL Schnelleinstieg
Mutations
34
GraphQL Schnelleinstieg
Mutations
• Mutationen werden verwendet um explizit schreibende
Zugriffe zu definieren
• Mutations verwenden ebenfalls die Query-Sprache
35
GraphQL Schnelleinstieg: Mutations
Eine Mutation funktioniert analog zu einer Query.
type Mutation {
createCompany(
name: String!
): Company
addLocation(
companyId: ID!,
location: LocationInput!
): Location
}
input LocationInput {
name: String!
address: AddressInput
}
input AddressInput {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
35
GraphQL Schnelleinstieg: Mutations
Eine Mutation funktioniert analog zu einer Query.
type Mutation {
createCompany(
name: String!
): Company
addLocation(
companyId: ID!,
location: LocationInput!
): Location
}
input LocationInput {
name: String!
address: AddressInput
}
input AddressInput {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
mutation {
createCompany(
name: „Microsoft"
) {
id
name
}
}
35
GraphQL Schnelleinstieg: Mutations
Eine Mutation funktioniert analog zu einer Query.
type Mutation {
createCompany(
name: String!
): Company
addLocation(
companyId: ID!,
location: LocationInput!
): Location
}
input LocationInput {
name: String!
address: AddressInput
}
input AddressInput {
addressLines: [String!]!
city: String!
postalCode: String!
Country: String!
}
mutation {
createCompany(
name: „Microsoft"
) {
id
name
}
}
{
"data": {
"createCompany": [
{
"id": "4711",
"name": "Microsoft"
}
]
}
}
36
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
37
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
}
type IssueCardResult {
id: ID!
amount: Int!
}
Schema
37
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
}
type IssueCardResult {
id: ID!
amount: Int!
}
Schema
mutation {
issueCard(amount: 256) {
id
}
}
Mutation
37
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
}
type IssueCardResult {
id: ID!
amount: Int!
}
Schema
mutation {
issueCard(amount: 256) {
id
}
}
Mutation
Response
{
"data": {
"issueCard": {
"id": "4711"
}
}
}
38
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
redeemCard(cardId: ID!, amount: Int!): RedeemCardResult
# ...
}
type RedeemCardResult {
remainingAmount: Int!
}
Schema
38
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
redeemCard(cardId: ID!, amount: Int!): RedeemCardResult
# ...
}
type RedeemCardResult {
remainingAmount: Int!
}
Schema
mutation {
redeemCard(
cardId: "4711",
amount: 20
) {
remainingAmount
}
}
Mutation
38
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
redeemCard(cardId: ID!, amount: Int!): RedeemCardResult
# ...
}
type RedeemCardResult {
remainingAmount: Int!
}
Schema
mutation {
redeemCard(
cardId: "4711",
amount: 20
) {
remainingAmount
}
}
Mutation
{
"data": {
"redeemCard": {
"remainingAmount":
236
}
}
}
Response
39
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
increaseCredit(cardId: ID!, amount: Int!):
IncreaseCreditResult
# ...
}
type RedeemCardResult {
remainingAmount: Int!
}
Schema
39
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
increaseCredit(cardId: ID!, amount: Int!):
IncreaseCreditResult
# ...
}
type RedeemCardResult {
remainingAmount: Int!
}
Schema
mutation {
increaseCredit(
cardId: "4711",
amount: 100
) {
remainingAmount
}
}
Mutation
39
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
increaseCredit(cardId: ID!, amount: Int!):
IncreaseCreditResult
# ...
}
type RedeemCardResult {
remainingAmount: Int!
}
Schema
mutation {
increaseCredit(
cardId: "4711",
amount: 100
) {
remainingAmount
}
}
Mutation
{
"data": {
"increaseCredit": {
"remainingAmount":
336
}
}
}
Response
40
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
issueCard(amount: Int!):
IssueCardResult
redeemCard(cardId: ID!, amount: Int!):
RedeemCardResult
increaseCredit(cardId: ID!, amount: Int!):
IncreaseCreditResult
revokeCard(cardId: ID!):
CardRevocationResult
revokeTransaction(cardId: ID!, transactionId: ID!):
TransactionRevocationResult
}
Schema
40
• Ausstellen neuer Karten
• Guthaben einlösen
• Guthaben erhöhen
• Karte stornieren
• Einlösung stornieren
type Mutation {
issueCard(amount: Int!):
IssueCardResult
redeemCard(cardId: ID!, amount: Int!):
RedeemCardResult
increaseCredit(cardId: ID!, amount: Int!):
IncreaseCreditResult
revokeCard(cardId: ID!):
CardRevocationResult
revokeTransaction(cardId: ID!, transactionId: ID!):
TransactionRevocationResult
}
Schema
Alle Interaktionen haben eine fachliche
Bezeichnung
41
GraphiQL
?
42
Was ist
CQRS/ES
43
CQRS/ES
44
CQRS/ES
Command & Query Responsibility Separation
45
Klassische Architekturen
• Es gibt ein zentrales Daten-Modell
• Der Zugriff auf alle relevanten Daten der
Applikation erfolgt über das Daten-Modell
• Das Daten-Modell wird sowohl für
schreibende als auch lesende Zugriffe
verwendet
• Damit verbunden muss dieses Datenmodell
auch alle Use Cases abbilden können
Data Model
Data
Store
W
r
i
t
e
R
e
a
d
46
Command & Query Responsibility Separation
Write
Data Model
Data
Store
Write
(Comma
nd)
46
Command & Query Responsibility Separation
• Mit CQRS, sind schreibende (commands)
und lesende (queries) Zugriffe getrennt
• Commands definieren, welche Änderung
durchgeführt werden sollen
• Die zugehörige Command Handler
implementiert die fachliche Logik
Write
Data Model
Data
Store
Write
(Comma
nd)
46
Command & Query Responsibility Separation
• Mit CQRS, sind schreibende (commands)
und lesende (queries) Zugriffe getrennt
• Commands definieren, welche Änderung
durchgeführt werden sollen
• Die zugehörige Command Handler
implementiert die fachliche Logik
• War ein Command erfolgreich, wird aus
dem neuen Zustand ein Read Model
abgeleitet (Projection)
Write
Data Model
Data
Store
Write
(Comma
nd)
Read
Data Model
Data
Store
Read
(Query
Projection
47
Read Model Projections
• Es gibt keine zwingende 1:1
Beziehung zwischen Write und Read
Model
• Es können beliebig viele Read
Models existieren
• Ein Read Model kann für einen
spezifischen Einsatzzweck optimiert
werden
Read
Data Model Y
Data
Store
Read
(Query
Read
Data Model X
Data
Store
Read
(Query
Projection
48
CQRS/ES
Event Sourcing
49
Sequenz der Events
• Beim Event Sourcing wird der Zustand der Daten in
Form einer Sequenz von Events abgebildet
• Diese Events werden nicht geändert
• Änderungen erfolgen durch das Hinzufügen neuer
Events
49
Sequenz der Events
• Beim Event Sourcing wird der Zustand der Daten in
Form einer Sequenz von Events abgebildet
• Diese Events werden nicht geändert
• Änderungen erfolgen durch das Hinzufügen neuer
Events
{
"FirstName": "Maria",
"LastName": "Strong"
}
CreateContactEvent
49
Sequenz der Events
• Beim Event Sourcing wird der Zustand der Daten in
Form einer Sequenz von Events abgebildet
• Diese Events werden nicht geändert
• Änderungen erfolgen durch das Hinzufügen neuer
Events
{
"FirstName": "Maria",
"LastName": "Strong"
}
CreateContactEvent
{
"LastName": "Fisher"
}
NameChangedEvent
49
Sequenz der Events
• Beim Event Sourcing wird der Zustand der Daten in
Form einer Sequenz von Events abgebildet
• Diese Events werden nicht geändert
• Änderungen erfolgen durch das Hinzufügen neuer
Events
{
"FirstName": "Maria",
"LastName": "Strong"
}
CreateContactEvent
{
"LastName": "Fisher"
}
NameChangedEvent
{
"email": "m.f@aol.com"
}
EMailAddressChangedEvent
50
CQRS/ES
Command & Query Responsibility Separation
Event Sourcing
kombiniert mit
51
Command Handling mit Events
Write Model
Command
Handling
Logic
Event
Store
51
Command Handling mit Events
Write Model
Command
Handling
Logic
Event
Store
1
1. Schreiboperation wird
gestartet (Command)
51
Command Handling mit Events
Write Model
Command
Handling
Logic
Event
Store
1
2
1. Schreiboperation wird
gestartet (Command)
2. Der Zustand des Write Model
wird aus der Sequenz der
Events wiederhergestellt
(sourced)
51
Command Handling mit Events
Write Model
Command
Handling
Logic
Event
Store
1
2
3
1. Schreiboperation wird
gestartet (Command)
2. Der Zustand des Write Model
wird aus der Sequenz der
Events wiederhergestellt
(sourced)
3. Die Command Handling
Logic vergleicht das
Command mit dem Write
Model und führt
51
Command Handling mit Events
Write Model
Command
Handling
Logic
Event
Store
1
2
3
4
1. Schreiboperation wird
gestartet (Command)
2. Der Zustand des Write Model
wird aus der Sequenz der
Events wiederhergestellt
(sourced)
3. Die Command Handling
Logic vergleicht das
Command mit dem Write
Model und führt
4. Änderungen werden als
Events abgebildet und in
einem Event Store
gespeichert
52
Mehr Möglichkeiten mit Projections
Write Model
Command
Handling
Logic
Event
Store
52
Mehr Möglichkeiten mit Projections
Write Model
Command
Handling
Logic
Event
Store
1
1. Events werden, wie
bereits beschrieben, im
Event Store gespeichert
52
Mehr Möglichkeiten mit Projections
Write Model
Command
Handling
Logic
Event
Store
2
1
1. Events werden, wie
bereits beschrieben, im
Event Store gespeichert
2. Bei der erfolgreichen
Persistierung, wird der
Event auch über einen
Event Bus verteilt
Event Bus
52
Mehr Möglichkeiten mit Projections
Write Model
Command
Handling
Logic
Event
Store
2
3
1
1. Events werden, wie
bereits beschrieben, im
Event Store gespeichert
2. Bei der erfolgreichen
Persistierung, wird der
Event auch über einen
Event Bus verteilt
3. Basierend auf diesen
Events, können viele
verschiedene
Projektionen erstellt oder
auch andere Integration
geschaffen werden
Event Bus
Read Model X Read Model Y Read Model Z
eMail
Notification
Analytics
&
53
CQRS/ES
GraphQL
54
GitHub: dxfrontiers/beyond-rest-and-crud-talk
55
Mutations
&
Mutation
Mapping
Queries
&
Query
Mapping
55
Mutations
&
Mutation
Mapping
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Mutations
&
Mutation
Mapping
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Mutations
&
Mutation
Mapping
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Event
Store
Mutations
&
Mutation
Mapping
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Event
Store
Event Bus
Mutations
&
Mutation
Mapping
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Event
Store
Event Bus
Mutations
&
Mutation
Mapping
View Model
Updater
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Event
Store
Event Bus
Mutations
&
Mutation
Mapping
View Model
Updater
(Spring Data)
Repository
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Event
Store
Event Bus
Mutations
&
Mutation
Mapping
View Model
Updater
DB
(Spring Data)
Repository
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Event
Store
Event Bus
Mutations
&
Mutation
Mapping
View Model
Updater
DB
(Spring Data)
Repository
Queries
&
Query
Mapping
55
Write Model
(Aggregate)
Command
Handling
Logic
Event
Store
Event Bus
Mutations
&
Mutation
Mapping
View Model
Updater
DB
(Spring Data)
Repository
Queries
&
Query
Mapping
56
IssueCardCommand
RedeemCardCommand
IncreaseCreditCommand
RevokeCardCommand
RevokeTransactionCommand
56
IssueCardCommand
RedeemCardCommand
IncreaseCreditCommand
RevokeCardCommand
RevokeTransactionCommand
CardIssuedEvent
CardRevokedEvent
RedeemedEvent
CreditIncreasedEvent
TransactionRevokedEvent
GiftCardTransaction
56
IssueCardCommand
RedeemCardCommand
IncreaseCreditCommand
RevokeCardCommand
RevokeTransactionCommand
CardIssuedEvent
CardRevokedEvent
RedeemedEvent
CreditIncreasedEvent
TransactionRevokedEvent
57
Mutations
58
@Controller
class MutationController(
private val gateway: CommandGateway
) {
@MutationMapping
fun issueCard(@Argument amount: Int): IssueCardResult {
val id = UUID.randomUUID().toString()
gateway.sendAndWait<Unit>(
IssueCardCommand(
id = id,
amount = amount
)
)
return IssueCardResult(id, amount)
}
}
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
}
59
@Controller
class MutationController(
private val gateway: CommandGateway
) {
@MutationMapping
fun issueCard(@Argument amount: Int): IssueCardResult {
val id = UUID.randomUUID().toString()
gateway.sendAndWait<Unit>(
IssueCardCommand(
id = id,
amount = amount
)
)
return IssueCardResult(id, amount)
}
}
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
} • Die Spring Boot Integration
für GraphQL basiert auf
dem Controller-Konzept
60
@Controller
class MutationController(
private val gateway: CommandGateway
) {
@MutationMapping
fun issueCard(@Argument amount: Int): IssueCardResult {
val id = UUID.randomUUID().toString()
gateway.sendAndWait<Unit>(
IssueCardCommand(
id = id,
amount = amount
)
)
return IssueCardResult(id, amount)
}
}
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
} • Die Spring Boot Integration
für GraphQL basiert auf
dem Controller-Konzept
61
@Controller
class MutationController(
private val gateway: CommandGateway
) {
@MutationMapping
fun issueCard(@Argument amount: Int): IssueCardResult {
val id = UUID.randomUUID().toString()
gateway.sendAndWait<Unit>(
IssueCardCommand(
id = id,
amount = amount
)
)
return IssueCardResult(id, amount)
}
}
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
} • Die Spring Boot Integration
für GraphQL basiert auf
dem Controller-Konzept
• GraphQL-Spezifische
Mapping-Annotationen
definieren die Handler
62
@Controller
class MutationController(
private val gateway: CommandGateway
) {
@MutationMapping
fun issueCard(@Argument amount: Int): IssueCardResult {
val id = UUID.randomUUID().toString()
gateway.sendAndWait<Unit>(
IssueCardCommand(
id = id,
amount = amount
)
)
return IssueCardResult(id, amount)
}
}
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
} • Die Spring Boot Integration
für GraphQL basiert auf
dem Controller-Konzept
• GraphQL-Spezifische
Mapping-Annotationen
definieren die Handler
• Zentrales Element sind die
Commands
63
@Controller
class MutationController(
private val gateway: CommandGateway
) {
@MutationMapping
fun issueCard(@Argument amount: Int): IssueCardResult {
val id = UUID.randomUUID().toString()
gateway.sendAndWait<Unit>(
IssueCardCommand(
id = id,
amount = amount
)
)
return IssueCardResult(id, amount)
}
}
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
} • Die Spring Boot Integration
für GraphQL basiert auf
dem Controller-Konzept
• GraphQL-Spezifische
Mapping-Annotationen
definieren die Handler
• Zentrales Element sind die
Commands
• Commands werden über
den CommandGateway
verschickt
64
@Controller
class MutationController(
private val gateway: CommandGateway
) {
@MutationMapping
fun issueCard(@Argument amount: Int): IssueCardResult {
val id = UUID.randomUUID().toString()
gateway.sendAndWait<Unit>(
IssueCardCommand(
id = id,
amount = amount
)
)
return IssueCardResult(id, amount)
}
}
type Mutation {
issueCard(amount: Int!): IssueCardResult
# ...
} • Die Spring Boot Integration
für GraphQL basiert auf
dem Controller-Konzept
• GraphQL-Spezifische
Mapping-Annotationen
definieren die Handler
• Zentrales Element sind die
Commands
• Commands werden über
den CommandGateway
verschickt
65
@MutationMapping
fun redeemCard(
@Argument cardId: String,
@Argument amount: Int): RedeemCardResult {
val transaction =
gateway.sendAndWait<GiftCardTransaction>(
RedeemCardCommand(
id = cardId,
amount = amount
)
)
return RedeemCardResult(transaction.remainingAmount)
}
type Mutation {
redeemCard(cardId: ID!, amount: Int!): RedeemCardResult
# ...
}
66
Command Handler
67
var amount by Delegates.notNull<Int>()
var revoked = false
@CommandHandler
fun handle(cmd: RedeemCardCommand): GiftCardTransaction {
if (revoked)
throw IllegalStateException("card has been revoked")
if (cmd.amount <= 0)
throw IllegalArgumentException(
"invalid amount ${cmd.amount}")
if (cmd.amount > amount)
throw IllegalArgumentException(
„insufficient card credit")
val transactionId = newTransactionId()
val e = RedeemedEvent(
id = id,
transactionId = transactionId,
amount = -cmd.amount,
remainingAmount = amount - cmd.amount
)
applyEvent(e)
return e
}
68
var amount by Delegates.notNull<Int>()
var revoked = false
@CommandHandler
fun handle(cmd: RedeemCardCommand): GiftCardTransaction {
if (revoked)
throw IllegalStateException("card has been revoked")
if (cmd.amount <= 0)
throw IllegalArgumentException(
"invalid amount ${cmd.amount}")
if (cmd.amount > amount)
throw IllegalArgumentException(
„insufficient card credit")
val transactionId = newTransactionId()
val e = RedeemedEvent(
id = id,
transactionId = transactionId,
amount = -cmd.amount,
remainingAmount = amount - cmd.amount
)
applyEvent(e)
return e
}
• Der Command Handler führ
eine Validierung des
Commands durch
69
var amount by Delegates.notNull<Int>()
var revoked = false
@CommandHandler
fun handle(cmd: RedeemCardCommand): GiftCardTransaction {
if (revoked)
throw IllegalStateException("card has been revoked")
if (cmd.amount <= 0)
throw IllegalArgumentException(
"invalid amount ${cmd.amount}")
if (cmd.amount > amount)
throw IllegalArgumentException(
„insufficient card credit")
val transactionId = newTransactionId()
val e = RedeemedEvent(
id = id,
transactionId = transactionId,
amount = -cmd.amount,
remainingAmount = amount - cmd.amount
)
applyEvent(e)
return e
}
• Der Command Handler führ
eine Validierung des
Commands durch
• Nach erfolgter Validierung
wird ein Event mit dem
neuen Zustand erzeugt
70
var amount by Delegates.notNull<Int>()
var revoked = false
@CommandHandler
fun handle(cmd: RedeemCardCommand): GiftCardTransaction {
if (revoked)
throw IllegalStateException("card has been revoked")
if (cmd.amount <= 0)
throw IllegalArgumentException(
"invalid amount ${cmd.amount}")
if (cmd.amount > amount)
throw IllegalArgumentException(
„insufficient card credit")
val transactionId = newTransactionId()
val e = RedeemedEvent(
id = id,
transactionId = transactionId,
amount = -cmd.amount,
remainingAmount = amount - cmd.amount
)
applyEvent(e)
return e
}
• Der Command Handler führ
eine Validierung des
Commands durch
• Nach erfolgter Validierung
wird ein Event mit dem
neuen Zustand erzeugt
71
Queries
72
type Query {
giftCard(id: ID!): GiftCard
}
type GiftCard {
id: ID!
amount: Int!
revoked: Boolean!
transactions: [GiftCardTransaction!]!
}
type GiftCardTransaction {
transactionId: ID!
amount: Int!
description: String!
}
73
@Entity
data class GiftCardTransactionEntity(
@Id
@GeneratedValue
var id: Long? = null,
var giftCardId: String,
var transactionId: Int,
var amount: Int,
@Column(length = 100)
var description: String
)
@Entity
data class GiftCardEntity(
@Id
var id: String,
var revoked: Boolean = false,
var amount: Int = 0
)
type Query {
giftCard(id: ID!): GiftCard
}
type GiftCard {
id: ID!
amount: Int!
revoked: Boolean!
transactions: [GiftCardTransaction!]!
}
type GiftCardTransaction {
transactionId: ID!
amount: Int!
description: String!
}
74
class GiftCardTransactionRepositoryUpdater(private val repo: GiftCardTransactionRepository) {
@EventHandler
fun on(e: CardIssuedEvent) {
// we create a "virtual" transaction that represents the issuing
repo.save(
GiftCardTransactionEntity(
giftCardId = e.id,
transactionId = 0,
amount = e.amount,
remainingAmount = e.amount,
description = "Gift Card issued"
)
)
}
@EventHandler
fun on(e: GiftCardTransaction) {
repo.save(
GiftCardTransactionEntity(
giftCardId = e.id,
transactionId = e.transactionId,
amount = e.amount,
description = when(e) {
is RedeemedEvent -> "Redeemed"
is CreditIncreasedEvent -> "Credit increased"
is TransactionRevokedEvent -> "Transaction ${e.revokedTransactionId} revoked"
else -> ""
}
)
)
}
}
75
@Controller
class QueryController(
private val giftCardRepository: GiftCardRepository,
private val giftCardTransactionRepository: GiftCardTransactionRepository
) {
@QueryMapping
fun giftCard(@Argument id: String) = giftCardRepository
.findById(id).orElseThrow {
GraphqlErrorException.Builder()
.errorClassification(ErrorType.NOT_FOUND)
.message("Gift card with id $id not found")
.build()
}
@SchemaMapping(typeName = "GiftCard", field = "transactions")
fun transactions(parent: GiftCardEntity) =
giftCardTransactionRepository
.findByGiftCardIdOrderByTransactionId(parent.id)
}
76
Thank you for listening!
Any Questions?
https://blog.digitalfrontiers.de
https://www.digitalfrontiers.de
@tellme_francois
github.com/fernanfs
François Fernandès
Senior Solution Architect
Photo by Matt Walsh on Unsplash

Contenu connexe

Similaire à Beyond REST and CRUD: CQRS/ES mit GraphQL

Oracle12c für Entwickler
Oracle12c für EntwicklerOracle12c für Entwickler
Oracle12c für EntwicklerCarsten Czarski
 
Oracle12c für Entwickler
Oracle12c für EntwicklerOracle12c für Entwickler
Oracle12c für Entwickleroraclebudb
 
Data Scraping with Excel - Campixx 2013 - Maik Schmidt
Data Scraping with Excel - Campixx 2013 - Maik SchmidtData Scraping with Excel - Campixx 2013 - Maik Schmidt
Data Scraping with Excel - Campixx 2013 - Maik SchmidtMaik Schmidt
 
Prometheus Monitoring
Prometheus MonitoringPrometheus Monitoring
Prometheus Monitoringinovex GmbH
 
Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?
Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?
Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?Marketing Factory
 
RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...
RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...
RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...Magnus Pfeffer
 
Einführung in NoSQL-Datenbanken
Einführung in NoSQL-DatenbankenEinführung in NoSQL-Datenbanken
Einführung in NoSQL-DatenbankenTobias Trelle
 
REST mit APEX 18.1
REST mit APEX 18.1REST mit APEX 18.1
REST mit APEX 18.1Oliver Lemm
 
Cloud Observability mit Loki, Prometheus, Tempo und Grafana
Cloud Observability mit Loki, Prometheus, Tempo und GrafanaCloud Observability mit Loki, Prometheus, Tempo und Grafana
Cloud Observability mit Loki, Prometheus, Tempo und GrafanaQAware GmbH
 
Einführung in Elasticsearch
Einführung in ElasticsearchEinführung in Elasticsearch
Einführung in ElasticsearchFlorian Hopf
 
DevOpsCon - Verteilte Entwicklung in Go
DevOpsCon - Verteilte Entwicklung in GoDevOpsCon - Verteilte Entwicklung in Go
DevOpsCon - Verteilte Entwicklung in GoFrank Müller
 
Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...
Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...
Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...OPEN KNOWLEDGE GmbH
 
Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)
Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)
Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)Ulrike Schwinn
 

Similaire à Beyond REST and CRUD: CQRS/ES mit GraphQL (14)

Oracle12c für Entwickler
Oracle12c für EntwicklerOracle12c für Entwickler
Oracle12c für Entwickler
 
Oracle12c für Entwickler
Oracle12c für EntwicklerOracle12c für Entwickler
Oracle12c für Entwickler
 
Data Scraping with Excel - Campixx 2013 - Maik Schmidt
Data Scraping with Excel - Campixx 2013 - Maik SchmidtData Scraping with Excel - Campixx 2013 - Maik Schmidt
Data Scraping with Excel - Campixx 2013 - Maik Schmidt
 
CKAN by Friedrich Lindenberg
CKAN by Friedrich LindenbergCKAN by Friedrich Lindenberg
CKAN by Friedrich Lindenberg
 
Prometheus Monitoring
Prometheus MonitoringPrometheus Monitoring
Prometheus Monitoring
 
Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?
Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?
Content-Inventur von großen Seiten. Wo gibt es noch Potentiale?
 
RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...
RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...
RVK 3.0 - Die Regensburger Verbundklassifikation als Normdatei für Bibliothek...
 
Einführung in NoSQL-Datenbanken
Einführung in NoSQL-DatenbankenEinführung in NoSQL-Datenbanken
Einführung in NoSQL-Datenbanken
 
REST mit APEX 18.1
REST mit APEX 18.1REST mit APEX 18.1
REST mit APEX 18.1
 
Cloud Observability mit Loki, Prometheus, Tempo und Grafana
Cloud Observability mit Loki, Prometheus, Tempo und GrafanaCloud Observability mit Loki, Prometheus, Tempo und Grafana
Cloud Observability mit Loki, Prometheus, Tempo und Grafana
 
Einführung in Elasticsearch
Einführung in ElasticsearchEinführung in Elasticsearch
Einführung in Elasticsearch
 
DevOpsCon - Verteilte Entwicklung in Go
DevOpsCon - Verteilte Entwicklung in GoDevOpsCon - Verteilte Entwicklung in Go
DevOpsCon - Verteilte Entwicklung in Go
 
Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...
Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...
Rufen Sie nicht an – wir rufen Sie an! | Server-sent Events und Web-Sockets i...
 
Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)
Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)
Überblick: 18c und Autonomous Data Warehouse Cloud (ADWC)
 

Beyond REST and CRUD: CQRS/ES mit GraphQL

  • 1. Beyond REST and CRUD: CQRS/ES mit GraphQL François Fernandes Senior Solution Architect francois.fernandes@digitalfrontiers.de @tellme_francois github.com/fernanfs
  • 2. ? 2 Was stimmt nicht mit REST und CRUD
  • 3. 3 Was ist denn eigentlich REST? • Alle relevanten Daten sind über eine URI erreichbar http://smart.service/api/resources/<resource-id> • Änderungen an den Daten werden mittels HTTP Requests und passenden Verben durchgeführt POST http://smart.service/api/resources/ GET http://smart.service/api/resources/<resource-id> DELETE http://smart.service/api/resources/<resource-id>
  • 4. 3 Was ist denn eigentlich REST? • Alle relevanten Daten sind über eine URI erreichbar http://smart.service/api/resources/<resource-id> • Änderungen an den Daten werden mittels HTTP Requests und passenden Verben durchgeführt POST http://smart.service/api/resources/ GET http://smart.service/api/resources/<resource-id> DELETE http://smart.service/api/resources/<resource-id> Kein REST ist: GET http://dumb.service/api/resources/<id>/applyColorBlue
  • 5. 4 Betrachten wir ein Beispiel Verwaltung für Gutscheinkarten
  • 6. 4 Betrachten wir ein Beispiel Verwaltung für Gutscheinkarten • Ausstellen neuer Karten
  • 7. 4 Betrachten wir ein Beispiel Verwaltung für Gutscheinkarten • Ausstellen neuer Karten • Guthaben einlösen
  • 8. 4 Betrachten wir ein Beispiel Verwaltung für Gutscheinkarten • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen
  • 9. 4 Betrachten wir ein Beispiel Verwaltung für Gutscheinkarten • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren
  • 10. 4 Betrachten wir ein Beispiel Verwaltung für Gutscheinkarten • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren
  • 11. 5 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren
  • 12. 6 Ausstellen neuer Karten • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren POST http://svc.local/api/giftcards Accept: application/json Content-Type: application/json { "amount": 42 } Request HTTP/1.1 200 OK Content-Type: application/json { "id": 102, "amount": 42 } Response
  • 13. 7 Guthaben einlösen • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren POST http://svc.local/api/giftcards/102/transactions Accept: application/json Content-Type: application/json { "amount": 11 } Request HTTP/1.1 201 CREATED Content-Type: application/json { "id": 203, "amount": -11, "description": "Redeem" } Response
  • 14. 8 Guthaben erhöhen • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren PUT http://svc.local/api/giftcards/102 Accept: application/json Content-Type: application/json { "amount": 80 } Request HTTP/1.1 201 CREATED Content-Type: application/json { "id": 102, "amount": 80 } Response
  • 15. 8 Guthaben erhöhen • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren PUT http://svc.local/api/giftcards/102 Accept: application/json Content-Type: application/json { "amount": 80 } Request HTTP/1.1 201 CREATED Content-Type: application/json { "id": 102, "amount": 80 } Response Ist das der Betrag um den das Guthaben erhöht werden soll? Oder ist es der neue Zielbetrag?
  • 16. 9 Karte stornieren • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren Was ist eigentlich eine Stornierung? • Löschung mittels DELETE? Der Datensatz sollte weiterhin existieren für eine Nachverfolgung • Eine Aktualisierung mittels PUT? Das steht in Konkurrenz zu Erhöhung des Guthabens
  • 17. 10 Karte stornieren • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren PUT http://svc.local/api/giftcards/102 Accept: application/json Content-Type: application/json { "revoked": true } Request HTTP/1.1 200 Content-Type: application/json { "id": 102, "amount": 80, "revoked": true } Response
  • 18. 11 Überlappung • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren @PutMapping("{id}") fun updateGiftCard( @PathVariable id: Long, @RequestBody input: GiftCardUpdateInput): GiftCardDto { return if (input.amount != null) service.increaseAmountTo(id, input.amount).asDto() else if (input.revoked == true) service.revokeCard(id).asDto() else // intention unclear throw UnknownGiftCardUpdateRequestException() }
  • 19. 12 Einlösung stornieren • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren Auf welcher Ressource (URI) operieren wir? • DELETE http://svc.local/api/giftcards/102/transactions/203 Löschung mittels DELETE • POST http://svc.local/api/giftcards/102/transactions Erstellen eines Storno-Datensatzes
  • 20. 13 Einlösung stornieren • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren DELETE http://svc.local/api/giftcards/102/transactions/203 • Der Datensatz soll nicht gelöscht werden • Auch wird der Datensatz selbst nicht geändert, sondern ein neuer angelegt
  • 21. 14 Einlösung stornieren • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren POST http://svc.local/api/giftcards/102/transactions • Der Body des POST könnte ein Flag tragen, das den Datensatz als Stornierung kennzeichnet • Steht in Konkurrenz zu normalen Transaktionen POST http://svc.local/api/giftcards/102/transactions Accept: application/json Content-Type: application/json { "amount": -11, „revokedTransactionId": 203 } Request
  • 23. 16 GraphQL Schnelleinstieg • GraphQL ist ein Mechanismus für die Kommunikation zwischen Client und Server • Die technische Übertragung ist typischerweise HTTP • Kern von GraphQL ist das GraphQL Schema • Das GraphQL Schema definiert, welche Daten der Server bereitstellen kann
  • 24. 17 GraphQL Schnelleinstieg: Das Schema • Das GraphQL Schema basiert auf Typen type Type { field: StrictType } type StrictType { valueNeverNull: String! } type PrimitiveFields { identifier: ID! numeric: Int! } type TypeWithArrays { strings: [String] strictList: [StrictType!]! }
  • 25. 18 GraphQL Schnelleinstieg: Das Schema • Das GraphQL Schema basiert auf Typen • Ein Typ besteht aus Feldern und deren Typisierung type Type { field: StrictType } type StrictType { valueNeverNull: String! } type PrimitiveFields { identifier: ID! numeric: Int! } type TypeWithArrays { strings: [String] strictList: [StrictType!]! }
  • 26. 19 GraphQL Schnelleinstieg: Das Schema • Das GraphQL Schema basiert auf Typen • Ein Typ besteht aus Feldern und deren Typisierung • Felder können explizit als non- nullable deklariert werden type Type { field: StrictType } type StrictType { valueNeverNull: String! } type PrimitiveFields { identifier: ID! numeric: Int! } type TypeWithArrays { strings: [String] strictList: [StrictType!]! }
  • 27. 20 GraphQL Schnelleinstieg: Das Schema • Das GraphQL Schema basiert auf Typen • Ein Typ besteht aus Feldern und deren Typisierung • Felder können explizit als non- nullable deklariert werden • Die Typen der Felder sind entweder deklarierte Typen oder Scalars type Type { field: StrictType } type StrictType { valueNeverNull: String! } type PrimitiveFields { identifier: ID! numeric: Int! } type TypeWithArrays { strings: [String] strictList: [StrictType!]! }
  • 28. 21 GraphQL Schnelleinstieg: Das Schema • Das GraphQL Schema basiert auf Typen • Ein Typ besteht aus Feldern und deren Typisierung • Felder können explizit als non- nullable deklariert werden • Die Typen der Felder sind entweder deklarierte Typen oder Scalars • Listen werden ebenfalls typisiert definiert type Type { field: StrictType } type StrictType { valueNeverNull: String! } type PrimitiveFields { identifier: ID! numeric: Int! } type TypeWithArrays { strings: [String] strictList: [StrictType!]! }
  • 29. 22 GraphQL Schnelleinstieg: Das Schema • Felder können Argumente erhalten • Anhand der Argumente kann der Zugriff auf das Feld konfiguriert werden type Type { field(filter: String!): ResultType other(filter: Filter!): ResultType } input Filter { text: String maxResults: Int }
  • 30. 23 GraphQL Schnelleinstieg: Das Schema • Felder können Argumente erhalten • Anhand der Argumente kann der Zugriff auf das Feld konfiguriert werden • Komplexere Argumente können in eigenen Input-Typen definiert werden type Type { field(filter: String!): ResultType other(filter: Filter!): ResultType } input Filter { text: String maxResults: Int }
  • 31. 24 GraphQL Schnelleinstieg: Das Schema Das GraphQL Schema bietet noch weitere Möglichkeiten • Union Types • Enum • Eigene Skalare • Interfaces
  • 32. 25 GraphQL Schnelleinstieg: Das Schema Das Schema definiert drei Einstiegspunkte in den Graphen:
  • 33. 26 GraphQL Schnelleinstieg: Das Schema Das Schema definiert drei Einstiegspunkte in den Graphen: • Query type Query { #... }
  • 34. 27 GraphQL Schnelleinstieg: Das Schema Das Schema definiert drei Einstiegspunkte in den Graphen: • Query • Mutation type Query { #... } type Mutation { #... }
  • 35. 28 GraphQL Schnelleinstieg: Das Schema Das Schema definiert drei Einstiegspunkte in den Graphen: • Query • Mutation • Subscription type Query { #... } type Mutation { #... } type Subscription { #... }
  • 36. 29 GraphQL Schnelleinstieg: Das Schema • Query ist der Einstieg in den Graphen • Daten die für den Client abrufbar sein sollen, müssen direkt oder indirekt über Query erreichbar sein • Die Komplexität des Graphen ist wenig limitiert type Query { companies: [Company!]! company(id: ID!): Company } type Company { id: ID! name: String! locations: [Location!]! } type Location { name: String! address: Address } type Address { addressLines: [String!]! city: String! postalCode: String! Country: String! }
  • 38. 31 GraphQL Schnelleinstieg Queries • Abfragen in dem Graphen werden mittels Queries definiert • Die Query-Sprache ist stark an dem GraphQL Schema angelehnt
  • 39. 32 GraphQL Schnelleinstieg: Queries Eine Query definiert, welche Daten aus dem Grafen ausgelesen werden sollen type Query { companies: [Company!]! company(id: ID!): Company } type Company { id: ID! name: String! locations: [Location!]! } type Location { name: String! address: Address } type Address { addressLines: [String!]! city: String! postalCode: String! Country: String! }
  • 40. 32 GraphQL Schnelleinstieg: Queries Eine Query definiert, welche Daten aus dem Grafen ausgelesen werden sollen type Query { companies: [Company!]! company(id: ID!): Company } type Company { id: ID! name: String! locations: [Location!]! } type Location { name: String! address: Address } type Address { addressLines: [String!]! city: String! postalCode: String! Country: String! } query { companies { name locations { name } } }
  • 41. 32 GraphQL Schnelleinstieg: Queries Eine Query definiert, welche Daten aus dem Grafen ausgelesen werden sollen type Query { companies: [Company!]! company(id: ID!): Company } type Company { id: ID! name: String! locations: [Location!]! } type Location { name: String! address: Address } type Address { addressLines: [String!]! city: String! postalCode: String! Country: String! } query { companies { name locations { name } } } { "data": { "companies": [ { "name": "Apple", "locations": [ { "name": "Campus" } ] } ] } }
  • 42. 32 GraphQL Schnelleinstieg: Queries Eine Query definiert, welche Daten aus dem Grafen ausgelesen werden sollen type Query { companies: [Company!]! company(id: ID!): Company } type Company { id: ID! name: String! locations: [Location!]! } type Location { name: String! address: Address } type Address { addressLines: [String!]! city: String! postalCode: String! Country: String! } query { companies { name locations { name } } } { "data": { "companies": [ { "name": "Apple", "locations": [ { "name": "Campus" } ] } ] } } Ein GraphQL Server antwortet nur mit den Daten, die explizit angefordert wurden
  • 44. 34 GraphQL Schnelleinstieg Mutations • Mutationen werden verwendet um explizit schreibende Zugriffe zu definieren • Mutations verwenden ebenfalls die Query-Sprache
  • 45. 35 GraphQL Schnelleinstieg: Mutations Eine Mutation funktioniert analog zu einer Query. type Mutation { createCompany( name: String! ): Company addLocation( companyId: ID!, location: LocationInput! ): Location } input LocationInput { name: String! address: AddressInput } input AddressInput { addressLines: [String!]! city: String! postalCode: String! Country: String! }
  • 46. 35 GraphQL Schnelleinstieg: Mutations Eine Mutation funktioniert analog zu einer Query. type Mutation { createCompany( name: String! ): Company addLocation( companyId: ID!, location: LocationInput! ): Location } input LocationInput { name: String! address: AddressInput } input AddressInput { addressLines: [String!]! city: String! postalCode: String! Country: String! } mutation { createCompany( name: „Microsoft" ) { id name } }
  • 47. 35 GraphQL Schnelleinstieg: Mutations Eine Mutation funktioniert analog zu einer Query. type Mutation { createCompany( name: String! ): Company addLocation( companyId: ID!, location: LocationInput! ): Location } input LocationInput { name: String! address: AddressInput } input AddressInput { addressLines: [String!]! city: String! postalCode: String! Country: String! } mutation { createCompany( name: „Microsoft" ) { id name } } { "data": { "createCompany": [ { "id": "4711", "name": "Microsoft" } ] } }
  • 48. 36 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren
  • 49. 37 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { issueCard(amount: Int!): IssueCardResult # ... } type IssueCardResult { id: ID! amount: Int! } Schema
  • 50. 37 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { issueCard(amount: Int!): IssueCardResult # ... } type IssueCardResult { id: ID! amount: Int! } Schema mutation { issueCard(amount: 256) { id } } Mutation
  • 51. 37 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { issueCard(amount: Int!): IssueCardResult # ... } type IssueCardResult { id: ID! amount: Int! } Schema mutation { issueCard(amount: 256) { id } } Mutation Response { "data": { "issueCard": { "id": "4711" } } }
  • 52. 38 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { redeemCard(cardId: ID!, amount: Int!): RedeemCardResult # ... } type RedeemCardResult { remainingAmount: Int! } Schema
  • 53. 38 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { redeemCard(cardId: ID!, amount: Int!): RedeemCardResult # ... } type RedeemCardResult { remainingAmount: Int! } Schema mutation { redeemCard( cardId: "4711", amount: 20 ) { remainingAmount } } Mutation
  • 54. 38 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { redeemCard(cardId: ID!, amount: Int!): RedeemCardResult # ... } type RedeemCardResult { remainingAmount: Int! } Schema mutation { redeemCard( cardId: "4711", amount: 20 ) { remainingAmount } } Mutation { "data": { "redeemCard": { "remainingAmount": 236 } } } Response
  • 55. 39 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { increaseCredit(cardId: ID!, amount: Int!): IncreaseCreditResult # ... } type RedeemCardResult { remainingAmount: Int! } Schema
  • 56. 39 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { increaseCredit(cardId: ID!, amount: Int!): IncreaseCreditResult # ... } type RedeemCardResult { remainingAmount: Int! } Schema mutation { increaseCredit( cardId: "4711", amount: 100 ) { remainingAmount } } Mutation
  • 57. 39 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { increaseCredit(cardId: ID!, amount: Int!): IncreaseCreditResult # ... } type RedeemCardResult { remainingAmount: Int! } Schema mutation { increaseCredit( cardId: "4711", amount: 100 ) { remainingAmount } } Mutation { "data": { "increaseCredit": { "remainingAmount": 336 } } } Response
  • 58. 40 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { issueCard(amount: Int!): IssueCardResult redeemCard(cardId: ID!, amount: Int!): RedeemCardResult increaseCredit(cardId: ID!, amount: Int!): IncreaseCreditResult revokeCard(cardId: ID!): CardRevocationResult revokeTransaction(cardId: ID!, transactionId: ID!): TransactionRevocationResult } Schema
  • 59. 40 • Ausstellen neuer Karten • Guthaben einlösen • Guthaben erhöhen • Karte stornieren • Einlösung stornieren type Mutation { issueCard(amount: Int!): IssueCardResult redeemCard(cardId: ID!, amount: Int!): RedeemCardResult increaseCredit(cardId: ID!, amount: Int!): IncreaseCreditResult revokeCard(cardId: ID!): CardRevocationResult revokeTransaction(cardId: ID!, transactionId: ID!): TransactionRevocationResult } Schema Alle Interaktionen haben eine fachliche Bezeichnung
  • 63. 44 CQRS/ES Command & Query Responsibility Separation
  • 64. 45 Klassische Architekturen • Es gibt ein zentrales Daten-Modell • Der Zugriff auf alle relevanten Daten der Applikation erfolgt über das Daten-Modell • Das Daten-Modell wird sowohl für schreibende als auch lesende Zugriffe verwendet • Damit verbunden muss dieses Datenmodell auch alle Use Cases abbilden können Data Model Data Store W r i t e R e a d
  • 65. 46 Command & Query Responsibility Separation Write Data Model Data Store Write (Comma nd)
  • 66. 46 Command & Query Responsibility Separation • Mit CQRS, sind schreibende (commands) und lesende (queries) Zugriffe getrennt • Commands definieren, welche Änderung durchgeführt werden sollen • Die zugehörige Command Handler implementiert die fachliche Logik Write Data Model Data Store Write (Comma nd)
  • 67. 46 Command & Query Responsibility Separation • Mit CQRS, sind schreibende (commands) und lesende (queries) Zugriffe getrennt • Commands definieren, welche Änderung durchgeführt werden sollen • Die zugehörige Command Handler implementiert die fachliche Logik • War ein Command erfolgreich, wird aus dem neuen Zustand ein Read Model abgeleitet (Projection) Write Data Model Data Store Write (Comma nd) Read Data Model Data Store Read (Query Projection
  • 68. 47 Read Model Projections • Es gibt keine zwingende 1:1 Beziehung zwischen Write und Read Model • Es können beliebig viele Read Models existieren • Ein Read Model kann für einen spezifischen Einsatzzweck optimiert werden Read Data Model Y Data Store Read (Query Read Data Model X Data Store Read (Query Projection
  • 70. 49 Sequenz der Events • Beim Event Sourcing wird der Zustand der Daten in Form einer Sequenz von Events abgebildet • Diese Events werden nicht geändert • Änderungen erfolgen durch das Hinzufügen neuer Events
  • 71. 49 Sequenz der Events • Beim Event Sourcing wird der Zustand der Daten in Form einer Sequenz von Events abgebildet • Diese Events werden nicht geändert • Änderungen erfolgen durch das Hinzufügen neuer Events { "FirstName": "Maria", "LastName": "Strong" } CreateContactEvent
  • 72. 49 Sequenz der Events • Beim Event Sourcing wird der Zustand der Daten in Form einer Sequenz von Events abgebildet • Diese Events werden nicht geändert • Änderungen erfolgen durch das Hinzufügen neuer Events { "FirstName": "Maria", "LastName": "Strong" } CreateContactEvent { "LastName": "Fisher" } NameChangedEvent
  • 73. 49 Sequenz der Events • Beim Event Sourcing wird der Zustand der Daten in Form einer Sequenz von Events abgebildet • Diese Events werden nicht geändert • Änderungen erfolgen durch das Hinzufügen neuer Events { "FirstName": "Maria", "LastName": "Strong" } CreateContactEvent { "LastName": "Fisher" } NameChangedEvent { "email": "m.f@aol.com" } EMailAddressChangedEvent
  • 74. 50 CQRS/ES Command & Query Responsibility Separation Event Sourcing kombiniert mit
  • 75. 51 Command Handling mit Events Write Model Command Handling Logic Event Store
  • 76. 51 Command Handling mit Events Write Model Command Handling Logic Event Store 1 1. Schreiboperation wird gestartet (Command)
  • 77. 51 Command Handling mit Events Write Model Command Handling Logic Event Store 1 2 1. Schreiboperation wird gestartet (Command) 2. Der Zustand des Write Model wird aus der Sequenz der Events wiederhergestellt (sourced)
  • 78. 51 Command Handling mit Events Write Model Command Handling Logic Event Store 1 2 3 1. Schreiboperation wird gestartet (Command) 2. Der Zustand des Write Model wird aus der Sequenz der Events wiederhergestellt (sourced) 3. Die Command Handling Logic vergleicht das Command mit dem Write Model und führt
  • 79. 51 Command Handling mit Events Write Model Command Handling Logic Event Store 1 2 3 4 1. Schreiboperation wird gestartet (Command) 2. Der Zustand des Write Model wird aus der Sequenz der Events wiederhergestellt (sourced) 3. Die Command Handling Logic vergleicht das Command mit dem Write Model und führt 4. Änderungen werden als Events abgebildet und in einem Event Store gespeichert
  • 80. 52 Mehr Möglichkeiten mit Projections Write Model Command Handling Logic Event Store
  • 81. 52 Mehr Möglichkeiten mit Projections Write Model Command Handling Logic Event Store 1 1. Events werden, wie bereits beschrieben, im Event Store gespeichert
  • 82. 52 Mehr Möglichkeiten mit Projections Write Model Command Handling Logic Event Store 2 1 1. Events werden, wie bereits beschrieben, im Event Store gespeichert 2. Bei der erfolgreichen Persistierung, wird der Event auch über einen Event Bus verteilt Event Bus
  • 83. 52 Mehr Möglichkeiten mit Projections Write Model Command Handling Logic Event Store 2 3 1 1. Events werden, wie bereits beschrieben, im Event Store gespeichert 2. Bei der erfolgreichen Persistierung, wird der Event auch über einen Event Bus verteilt 3. Basierend auf diesen Events, können viele verschiedene Projektionen erstellt oder auch andere Integration geschaffen werden Event Bus Read Model X Read Model Y Read Model Z eMail Notification Analytics
  • 101. 58 @Controller class MutationController( private val gateway: CommandGateway ) { @MutationMapping fun issueCard(@Argument amount: Int): IssueCardResult { val id = UUID.randomUUID().toString() gateway.sendAndWait<Unit>( IssueCardCommand( id = id, amount = amount ) ) return IssueCardResult(id, amount) } } type Mutation { issueCard(amount: Int!): IssueCardResult # ... }
  • 102. 59 @Controller class MutationController( private val gateway: CommandGateway ) { @MutationMapping fun issueCard(@Argument amount: Int): IssueCardResult { val id = UUID.randomUUID().toString() gateway.sendAndWait<Unit>( IssueCardCommand( id = id, amount = amount ) ) return IssueCardResult(id, amount) } } type Mutation { issueCard(amount: Int!): IssueCardResult # ... } • Die Spring Boot Integration für GraphQL basiert auf dem Controller-Konzept
  • 103. 60 @Controller class MutationController( private val gateway: CommandGateway ) { @MutationMapping fun issueCard(@Argument amount: Int): IssueCardResult { val id = UUID.randomUUID().toString() gateway.sendAndWait<Unit>( IssueCardCommand( id = id, amount = amount ) ) return IssueCardResult(id, amount) } } type Mutation { issueCard(amount: Int!): IssueCardResult # ... } • Die Spring Boot Integration für GraphQL basiert auf dem Controller-Konzept
  • 104. 61 @Controller class MutationController( private val gateway: CommandGateway ) { @MutationMapping fun issueCard(@Argument amount: Int): IssueCardResult { val id = UUID.randomUUID().toString() gateway.sendAndWait<Unit>( IssueCardCommand( id = id, amount = amount ) ) return IssueCardResult(id, amount) } } type Mutation { issueCard(amount: Int!): IssueCardResult # ... } • Die Spring Boot Integration für GraphQL basiert auf dem Controller-Konzept • GraphQL-Spezifische Mapping-Annotationen definieren die Handler
  • 105. 62 @Controller class MutationController( private val gateway: CommandGateway ) { @MutationMapping fun issueCard(@Argument amount: Int): IssueCardResult { val id = UUID.randomUUID().toString() gateway.sendAndWait<Unit>( IssueCardCommand( id = id, amount = amount ) ) return IssueCardResult(id, amount) } } type Mutation { issueCard(amount: Int!): IssueCardResult # ... } • Die Spring Boot Integration für GraphQL basiert auf dem Controller-Konzept • GraphQL-Spezifische Mapping-Annotationen definieren die Handler • Zentrales Element sind die Commands
  • 106. 63 @Controller class MutationController( private val gateway: CommandGateway ) { @MutationMapping fun issueCard(@Argument amount: Int): IssueCardResult { val id = UUID.randomUUID().toString() gateway.sendAndWait<Unit>( IssueCardCommand( id = id, amount = amount ) ) return IssueCardResult(id, amount) } } type Mutation { issueCard(amount: Int!): IssueCardResult # ... } • Die Spring Boot Integration für GraphQL basiert auf dem Controller-Konzept • GraphQL-Spezifische Mapping-Annotationen definieren die Handler • Zentrales Element sind die Commands • Commands werden über den CommandGateway verschickt
  • 107. 64 @Controller class MutationController( private val gateway: CommandGateway ) { @MutationMapping fun issueCard(@Argument amount: Int): IssueCardResult { val id = UUID.randomUUID().toString() gateway.sendAndWait<Unit>( IssueCardCommand( id = id, amount = amount ) ) return IssueCardResult(id, amount) } } type Mutation { issueCard(amount: Int!): IssueCardResult # ... } • Die Spring Boot Integration für GraphQL basiert auf dem Controller-Konzept • GraphQL-Spezifische Mapping-Annotationen definieren die Handler • Zentrales Element sind die Commands • Commands werden über den CommandGateway verschickt
  • 108. 65 @MutationMapping fun redeemCard( @Argument cardId: String, @Argument amount: Int): RedeemCardResult { val transaction = gateway.sendAndWait<GiftCardTransaction>( RedeemCardCommand( id = cardId, amount = amount ) ) return RedeemCardResult(transaction.remainingAmount) } type Mutation { redeemCard(cardId: ID!, amount: Int!): RedeemCardResult # ... }
  • 110. 67 var amount by Delegates.notNull<Int>() var revoked = false @CommandHandler fun handle(cmd: RedeemCardCommand): GiftCardTransaction { if (revoked) throw IllegalStateException("card has been revoked") if (cmd.amount <= 0) throw IllegalArgumentException( "invalid amount ${cmd.amount}") if (cmd.amount > amount) throw IllegalArgumentException( „insufficient card credit") val transactionId = newTransactionId() val e = RedeemedEvent( id = id, transactionId = transactionId, amount = -cmd.amount, remainingAmount = amount - cmd.amount ) applyEvent(e) return e }
  • 111. 68 var amount by Delegates.notNull<Int>() var revoked = false @CommandHandler fun handle(cmd: RedeemCardCommand): GiftCardTransaction { if (revoked) throw IllegalStateException("card has been revoked") if (cmd.amount <= 0) throw IllegalArgumentException( "invalid amount ${cmd.amount}") if (cmd.amount > amount) throw IllegalArgumentException( „insufficient card credit") val transactionId = newTransactionId() val e = RedeemedEvent( id = id, transactionId = transactionId, amount = -cmd.amount, remainingAmount = amount - cmd.amount ) applyEvent(e) return e } • Der Command Handler führ eine Validierung des Commands durch
  • 112. 69 var amount by Delegates.notNull<Int>() var revoked = false @CommandHandler fun handle(cmd: RedeemCardCommand): GiftCardTransaction { if (revoked) throw IllegalStateException("card has been revoked") if (cmd.amount <= 0) throw IllegalArgumentException( "invalid amount ${cmd.amount}") if (cmd.amount > amount) throw IllegalArgumentException( „insufficient card credit") val transactionId = newTransactionId() val e = RedeemedEvent( id = id, transactionId = transactionId, amount = -cmd.amount, remainingAmount = amount - cmd.amount ) applyEvent(e) return e } • Der Command Handler führ eine Validierung des Commands durch • Nach erfolgter Validierung wird ein Event mit dem neuen Zustand erzeugt
  • 113. 70 var amount by Delegates.notNull<Int>() var revoked = false @CommandHandler fun handle(cmd: RedeemCardCommand): GiftCardTransaction { if (revoked) throw IllegalStateException("card has been revoked") if (cmd.amount <= 0) throw IllegalArgumentException( "invalid amount ${cmd.amount}") if (cmd.amount > amount) throw IllegalArgumentException( „insufficient card credit") val transactionId = newTransactionId() val e = RedeemedEvent( id = id, transactionId = transactionId, amount = -cmd.amount, remainingAmount = amount - cmd.amount ) applyEvent(e) return e } • Der Command Handler führ eine Validierung des Commands durch • Nach erfolgter Validierung wird ein Event mit dem neuen Zustand erzeugt
  • 115. 72 type Query { giftCard(id: ID!): GiftCard } type GiftCard { id: ID! amount: Int! revoked: Boolean! transactions: [GiftCardTransaction!]! } type GiftCardTransaction { transactionId: ID! amount: Int! description: String! }
  • 116. 73 @Entity data class GiftCardTransactionEntity( @Id @GeneratedValue var id: Long? = null, var giftCardId: String, var transactionId: Int, var amount: Int, @Column(length = 100) var description: String ) @Entity data class GiftCardEntity( @Id var id: String, var revoked: Boolean = false, var amount: Int = 0 ) type Query { giftCard(id: ID!): GiftCard } type GiftCard { id: ID! amount: Int! revoked: Boolean! transactions: [GiftCardTransaction!]! } type GiftCardTransaction { transactionId: ID! amount: Int! description: String! }
  • 117. 74 class GiftCardTransactionRepositoryUpdater(private val repo: GiftCardTransactionRepository) { @EventHandler fun on(e: CardIssuedEvent) { // we create a "virtual" transaction that represents the issuing repo.save( GiftCardTransactionEntity( giftCardId = e.id, transactionId = 0, amount = e.amount, remainingAmount = e.amount, description = "Gift Card issued" ) ) } @EventHandler fun on(e: GiftCardTransaction) { repo.save( GiftCardTransactionEntity( giftCardId = e.id, transactionId = e.transactionId, amount = e.amount, description = when(e) { is RedeemedEvent -> "Redeemed" is CreditIncreasedEvent -> "Credit increased" is TransactionRevokedEvent -> "Transaction ${e.revokedTransactionId} revoked" else -> "" } ) ) } }
  • 118. 75 @Controller class QueryController( private val giftCardRepository: GiftCardRepository, private val giftCardTransactionRepository: GiftCardTransactionRepository ) { @QueryMapping fun giftCard(@Argument id: String) = giftCardRepository .findById(id).orElseThrow { GraphqlErrorException.Builder() .errorClassification(ErrorType.NOT_FOUND) .message("Gift card with id $id not found") .build() } @SchemaMapping(typeName = "GiftCard", field = "transactions") fun transactions(parent: GiftCardEntity) = giftCardTransactionRepository .findByGiftCardIdOrderByTransactionId(parent.id) }
  • 119. 76 Thank you for listening! Any Questions? https://blog.digitalfrontiers.de https://www.digitalfrontiers.de @tellme_francois github.com/fernanfs François Fernandès Senior Solution Architect Photo by Matt Walsh on Unsplash